-
Notifications
You must be signed in to change notification settings - Fork 199
Thermistor config #208
base: experimental
Are you sure you want to change the base?
Thermistor config #208
Conversation
First commit applied, nice finding. Had to keep the first of the removed lines, because step is used. |
Regarding the second patch. The algorithm is an excellent idea and actually sounds familiar to me: Traumflug/Visolate@6b53bbe I've applied the commit with some whitespace edits and by adding the description from here. I think I did so correctly, but when creating a table with this config.h (both included files are part of the distribution) ... // Configuration for controller board.
#include "config/board.nanoheart-v1.0.h"
// Configuration for printer board.
#include "config/printer.mendel.h" ... a build fails, because there's a comma in line 36 missing: [...]
{ 110, 820}, // 205 C, 570 ohms, 0.537 V, 0.51 mW
{ 147, 748}, // 187 C, 797 ohms, 0.718 V, 0.65 mW
{ 246, 624} // 156 C, 1515 ohms, 1.201 V, 0.95 mW
{ 401, 504}, // 126 C, 3103 ohms, 1.958 V, 1.24 mW
{ 750, 308}, // 77 C, 13026 ohms, 3.662 V, 1.03 mW
[...] So I put this into a new branch, thermistor-config. Do you have an idea on what's going wrong? Also, having the same algorithm for the supposedly more accurate Steinhart-Hart table (also in thermistortablefile.py) would be awesome. Looking at the graphs above it looks like a good idea to swap the thermistor comparison resistor, typically 4K7, to a smaller one, e.g. 1K or 560R. Between 250°C and 350°C you use only 1 or 2% of ADC's accuracy, which isn't much. But that's not a Teacup issue. |
P.S. Thinking about it, it might be a good idea to limit the table to 0..500 °C. The new algorithm undoubtly gives a more precise table, but 9 entries of 25 are pretty useless, because above 350 °C. Also, as accuracy is calculated now, it might be a good idea to ignore NUM_TEMPS and use only enough entries to achieve a reasonable accuracy (2°C ?, 1% ? ). Smaller tables are faster. |
Temperature tables are emitted by selecting arbitrary sample values to be used for the linear lookup table. This is fine in the range where the thermistor produces linear output, but it is markedly wrong near the extremes where the thermister output begins to curve. Introduce a new sample selector which chooses samples based on the "most incorrect estimate" and improves from there to ensure we get a cleaner approximation across the selected range. Traumflug: this topic is tracked here: #208
As I just have the temp table lookup code in front of me for applying this other pull request: this part is probably too much work for little gain, NUMTEMPS isn't easily changeable to a per-sensor value. |
If you're looking for speed from the temp table, a binary search would be an obvious first step. The current linear search is needlessly slow most of the time. |
Not that much slow as it seems. You normally print over 200°C. That are just 4 steps. On a normal binary you need more. Maybe for a print bed. Hmmm... |
Temperature tables are emitted by selecting arbitrary sample values to be used for the linear lookup table. This is fine in the range where the thermistor produces linear output, but it is markedly wrong near the extremes where the thermister output begins to curve. Introduce a new sample selector which chooses samples based on the "most incorrect estimate" and improves from there to ensure we get a cleaner approximation across the selected range. Traumflug: this topic is tracked here: #208
efb35ad
to
71ee7ce
Compare
In the optimized sample set, 200C appears after 15 samples. But a binary search will consider only 5 samples. I don't think this is a good optimization to consider. The lookup should be fast in any case, and always faster than the division which follows it. |
These latest commits fix the misplaced-comma bug and include the optimization for the Reinhart-Hart algorithm, too. I don't have any examples of valid Reinhart values to try, but I'm reasonably certain it should work for reasonably correct sample data. I think there are bugs when the Reinhart algorithm returns invalid data, though. |
Ah ok. Was a little bit confused because we start with j = 1. On Marlin, they uses a lookup table to calculate the delays for the stepper timer. This table has also a delta between the values. Like:
This could also improve the performance. https://github.com/Wurstnase/Marlin/blob/Development/Marlin/stepper.cpp#L505-L513 |
We could save the reciprocal as fixpoint. Just made a small test with my local temptable. 195 and 13 could be saved in the temptable also to minimize the error. Everything could be calculated in the createTemperatureLookup.py |
If you're that keen on accurate conversions, there's still the thermistotable branch(es). This removes the table entirely in favor of a direct runtime calculation. Performance isn't that bad, 2500 ticks for the calculation vs. 1800 ticks for the interpolation. |
Temperature tables are emitted by selecting arbitrary sample values to be used for the linear lookup table. This is fine in the range where the thermistor produces linear output, but it is markedly wrong near the extremes where the thermister output begins to curve. Introduce a new sample selector which chooses samples based on the "most incorrect estimate" and improves from there to ensure we get a cleaner approximation across the selected range. Traumflug: this topic is tracked here: #208
Thank you very much, all applied to experimental and thermistor-config removed. |
This is the error between 195 >> 13 and the division by 42. Not accuracy. Just speed. |
I was thinking of precalculating the deltas also but it will break everyone's existing temperature tables if we implement that in the code unless we do it in a separate table. |
If you're into speed, avoiding this calculation alltogether is likely the best option. Instead of running PID against Celsius values it can run just as well against raw readings. Math is a bit different, of course.
I wouldn't mind too much "breaking" older tables, because they're now created on the fly when using Configtool. |
Which not everyone uses. |
Yes, this should be the next step. Anyhow, the speed of the table can be improved.
In temp.c you read temptable[num][x][0|1]. So it should be no issue to add one extra element? Edit: Ah got you. Yes, but progress needs sometimes breaking older things. P.S.: Tested that new temptable. Looks pretty nice! |
Old code reading new tables (3-values wide) is not a problem. New code reading old tables (2-values wide) could be a problem. But it could be mitigated with clever #if's. I'm looking into it now. |
Teach simulator to calculate temperatures for all possible ADC values using the compiled-in temperature tables and report the resulting conversion. Do no other run-time simulation; exit after reporting the conversion results. Output is suitable for gnuplot for example like this: gnuplot --persist -e "plot '< ./sim -T0' u 1:2 with lines, '< ./sim -T1' u 1:2 with lines"
The Thermistortablefile.py routine prematurely drops the fractional part of the temperature when computing the 14.2 temperature values to emit in the code. Keep this instead until the last moment when we finally calculate the integer format we will store.
Save a division at runtime by pre-calculating the slope between each pair of adjacent thermistortable values. Since we use the larger value each time, save the slope between two values A and B in the table with the B data. Therefore the slope is that between each value and its predecessor in the list. Store this new value in the third element of the now 3-integers-wide array which makes up the table. Use fixed-point 6.10 format to store the slope. This is almost too narrow for some slopes and maybe it should be changed to 8.8 fixed-point. In practice this presents a loss in accuracy, but it is still significantly better than the previous fixed-sample-size table production method. In particular no provision is made to handle values which scale over 65535, and it seems we should at least warn about this if not simply fail before letting the user go off compiling his code. Add a new flag TEMPTABLE_FORMAT and define it as 1 to tell the code that we are using this new and incompatible format. This lets us tolerate old hand-crafted thermistor tables by keeping the slower algorithm in case one is still used. New thermistor tables should be defined with this new format and with the FORMAT define set accordingly. With the default 25 samples this adds 100 bytes to the flash image for the thermistortable storage for two different thermistors. But the code is simplified and saves me 134 bytes in the bargain for a net decrease in flash size of 34 bytes.
71ee7ce
to
df3d827
Compare
Something like this, perhaps. df3d827 I've only tested this in the simulator so far. But the simulator is now able to report the calculated temperatures for every ADC value, and I used that to compare the before and after temperature profiles for this code change. |
And this change includes the binary search optimizer. |
Use a binary search to find our target temperate in fewer comparisons. The search algorithm is ostensibly slower because it involves a division, but it's a div-by-two so should be optimized into a simple bit-shift. Fewer comparisons involves fewer pgm_read_words and should be faster overall, but the gain is arguably tiny.
083099f
to
db997b4
Compare
Hopefully I'm doing anything right with the simulAVR. So here we see some numbers. TEMPTABLE_FORMAT 1
TEMPTABLE_FORMAT 0
old naive search added in temp_table_lookup() from phord
current experimental
|
I'm excited to see you doing performance measurements! However I'm scratching a bit my head on how you did this. For measuring temperature conversion performance I did something like this in earlier attempts:
volatile uint16_t result;
for (uint16_t i = 0; i < 1024; i++) {
ATOMIC_START
WRITE(DEBUG_LED, 1);
result = temp_table_lookup(i);
WRITE(DEBUG_LED, 0);
ATOMIC_END
}
This should give exactly 1024 LED on occurences. But you got 710 of them, so you didn't change the code other than applying patches? In this case you still measure performance of the step interrupt. But why does this interrupt get faster, then? |
I moved the debug led to temp.c, this was maybe wrong. I will test your idea, too. |
Ok, here we go: TEMPTABLE_FORMAT 1
TEMPTABLE_FORMAT 0
current lookup.
|
Thanks for the repetition. Looks like my headscratching was mostly unfounded. Now, this reduction from 1770 ticks average to 470 ticks average is impressing, isn't it? This additional #define still bugs me a bit, let me see wether I can solve this with a sizeof(). |
Good. Using a sizeof(temptable[0][0]) detects the type of table fine. Other than that I hope I've picked everything to experimental correctly. Temperature conversions are 3.7-fold faster now :-) Are we done with this issue? |
Really impressive work from @phord ! |
Heh. I had "if ... sizeof" too but I didn't want the code to be bloated with two algorithms. It didn't occur to me to check if the compiler would completely optimize out the unused one. Thanks for the cleanup. |
Hi, I've tested experimental branch and hit into a problem regarding pre-computed slope in thermistor table.
I generated table for NTC and used my old table without a slope precomputed for PTC so I ended with following config:
As you can see one one table contains pre-computed slope while the other doesn't. Unfortunately there's no compilation warning. GCC silently assumes missing table value (slope) to be 0. IN result my bed temperature reading have no interpolation and was jumping from one sample to the other. I did some debugging I found the reason and I thought "Ok, this is smart, I'll precompute slope for my PTC as well". And here's the problem: NTCs have falling slope, so D is negative. Table values are unsigned integers therefore we keep the D inverted there and invert it back during interpolation (no comment about it in a code nor in generated table!) PTCs have raising slope, so D is positive and using the representation I would need to put negative numbers into unsigned int table... I would propose to change entry in a table from triplet of unsigned ints into a structure of uint16, uint16 and signed int32. Signed int would solve problem with PTC/NTC combination. 32-bit int would solve a problem with precision mentioned above. |
My thoughts? Excellent bug report! It just could have been put into a new one. The only reason I can see to not use an int32_t is increased binary size (~100 bytes for two tables) and slightly slower computation. My guess is some additional 10 clocks per computation for reading the value from flash only, because temp.c line 247 is a 32-bit operation already. If I could find a proof that an int16_t is sufficient for all realistic cases I'd use this. Either way would add support for PTCs at a reasonable cost. Regarding distinct table lengths I'm not sure what the point would be other than saving a couple of bytes binary size. Graphs above show how accurate 25 values are already. Precision isn't crucial, regulation should work with coarse tables, too, so it's mostly a matter of the number shown on the display. Do you feel like crafting a patch? |
@wsowa Interesting and unexpected side-effect of zero-filled array padding. Thanks for the detailed analysis. Using a structure is the right way forward, but doesn't solve the problem of zero-filling omitted values. A run-time check for zeroes could, but this wouldn't have the same code-space savings as we got with @Traumflug's if/else trick. I guess we need to bite the bullet either way. Or else we could add a g++ dependency and use templates to build this table. 😁 I'm not convinced we need any more precision in the slope value. We only need to reach a precision of 0.25 anyway. 15-bits of slope leaves us enough room. We have a 10-bit exponent now allowing a slope up to 32 in 5 more bits. But we can do better if the exponent is smaller. We want 2 bits of exponent in the final calculation which leaves us with a range of 8 bits (256) to cover our interpolation point differences. That's pretty huge on a total range which is only 1024 long (10-bit ADC). We could easily get by with only 5+2 bits of exponent without any real precision lost, I think. |
Some experiments here: https://github.com/Traumflug/Teacup_Firmware/tree/temptable-fixup |
b491ab4
to
974c4b7
Compare
I noticed the Configtool produces temperature lookup tables which are significantly wrong around 300C where I print nylon and polycarbonate. I have a list of the correct ADC conversion values for all 1024 possible inputs and you can see the error on this graph.
I wrote an algorithm that works like this:
The updated graph is a much better approximation using the same number of samples: