From 5c3cfeeae3bcdb24ed965c901e260dea5f819421 Mon Sep 17 00:00:00 2001 From: Phil Hord Date: Thu, 14 Apr 2016 17:44:16 -0400 Subject: [PATCH 1/4] sim: Add --report-temptables option 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" --- simulator.h | 1 + simulator/analog_sim.c | 30 ++++++++++++++++++++++++++++-- simulator/simulator.c | 7 ++++++- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/simulator.h b/simulator.h index 1491a518d..6392706ed 100644 --- a/simulator.h +++ b/simulator.h @@ -142,6 +142,7 @@ void sim_error(const char msg[]); void sim_assert(bool cond, const char msg[]); void sim_gcode_ch(char ch); void sim_gcode(const char msg[]); +void sim_report_temptables(int sensor) ; /** * Initialize simulator timer and set time scale. diff --git a/simulator/analog_sim.c b/simulator/analog_sim.c index 7cef91133..eea4d8428 100644 --- a/simulator/analog_sim.c +++ b/simulator/analog_sim.c @@ -1,15 +1,41 @@ +#include "temp.h" #include "analog.h" #include "simulator.h" +#include static bool analog_initialised = false; void analog_init(void) { - sim_info("analog_init: not implemented in simulator"); analog_initialised = true; } +static uint16_t analog_read_value = 0; uint16_t analog_read(uint8_t channel) { sim_assert(analog_initialised, "analog_init() was not called before analog_read()"); sim_assert(sim_interrupts, "interrupts disabled"); - return 0; + return analog_read_value; +} + +void sim_report_temptables(int sensor) { + int i ; + temp_sensor_t s, first = sensor, last = sensor+1 ; + + // sensor is a specific sensor or -1 for "all sensors" + if (sensor == -1) { + first = 0; + last = NUM_TEMP_SENSORS; + } + + sei(); + analog_init(); + printf("; Temperature sensor test %d\n", sensor); + for (s = first; s < last; s++ ) { + printf("; Sensor %d\n", s); + for (i = 0 ; i < 1024 ; i++ ) { + analog_read_value = i ; + temp_sensor_tick() ; + uint16_t temp = temp_get(s); + printf("%d %.2f\n", i, ((float)temp)/4 ) ; + } + } } diff --git a/simulator/simulator.c b/simulator/simulator.c index 96ee137ef..b157b47bd 100644 --- a/simulator/simulator.c +++ b/simulator/simulator.c @@ -45,7 +45,7 @@ int verbose = 1; ///< 0=quiet, 1=normal, 2=noisy, 3=debug, etc. int trace_gcode = 0; ///< show gcode on the console int trace_pos = 0; ///< show print head position on the console -const char * shortopts = "qgpvt:o::"; +const char * shortopts = "qgpvt:o::T::"; struct option opts[] = { { "quiet", no_argument, &verbose , 0 }, { "verbose", no_argument, NULL, 'v' }, @@ -53,6 +53,7 @@ struct option opts[] = { { "pos", no_argument, NULL, 'p' }, { "time-scale", required_argument, NULL, 't' }, { "tracefile", optional_argument, NULL, 'o' }, + { "report-temptable", optional_argument, NULL, 'T' }, { 0, 0, 0, 0 } }; @@ -65,6 +66,7 @@ static void usage(const char *name) { printf(" -p || --pos show head position on console\n"); printf(" -t || --time-scale=n set time-scale; 0=warp-speed, 1=real-time, 2=half-time, etc.\n"); printf(" -o || --tracefile[=filename] write simulator pin trace to 'outfile' (default filename=datalog.out)\n"); + printf(" -T || --report-temptable=n Report calculated temperatures for all ADC values and exit\n"); printf("\n"); exit(1); } @@ -96,6 +98,9 @@ void sim_start(int argc, char** argv) { case 'o': recorder_init(optarg ? optarg : "datalog.out"); break; + case 'T': + sim_report_temptables(optarg ? atoi(optarg) : -1); + exit(0); default: exit(1); } From e5045683f1add3c704cd66e065c82b777c9b1f33 Mon Sep 17 00:00:00 2001 From: Phil Hord Date: Thu, 14 Apr 2016 18:01:06 -0400 Subject: [PATCH 2/4] Keep fractional part when computing thermistortable 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. --- configtool/thermistortablefile.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/configtool/thermistortablefile.py b/configtool/thermistortablefile.py index 27f2348b2..e03355a55 100644 --- a/configtool/thermistortablefile.py +++ b/configtool/thermistortablefile.py @@ -103,7 +103,7 @@ def BetaTable(ofp, params, names, settings, finalTable): samples = optimizeTempTable(thrm, N, hiadc) for i in samples: - t = int(thrm.temp(i)) + t = thrm.temp(i) if t is None: ofp.output("// ERROR CALCULATING THERMISTOR VALUES AT ADC %d" % i) continue @@ -118,7 +118,7 @@ def BetaTable(ofp, params, names, settings, finalTable): else: c = "," ostr = (" {%4s, %5s}%s // %4d C, %6.0f ohms, %0.3f V," - " %0.2f mW") % (i, t * 4, c, t, int(round(r)), + " %0.2f mW") % (i, int(t * 4), c, int(t), int(round(r)), vTherm, ptherm * 1000) ofp.output(ostr) @@ -146,7 +146,7 @@ def SteinhartHartTable(ofp, params, names, settings, finalTable): samples = optimizeTempTable(thrm, N, hiadc) for i in samples: - t = int(thrm.temp(i)) + t = thrm.temp(i) if t is None: ofp.output("// ERROR CALCULATING THERMISTOR VALUES AT ADC %d" % i) continue @@ -158,7 +158,7 @@ def SteinhartHartTable(ofp, params, names, settings, finalTable): else: c = "," ofp.output(" {%4d, %5d}%s // %4d C, %6d ohms" % - (i, t * 4, c, t, int(round(r)))) + (i, int(t * 4), c, int(t), int(round(r)))) if finalTable: ofp.output(" }") From df3d827a361075a976c3c98767db001dc3f008df Mon Sep 17 00:00:00 2001 From: Phil Hord Date: Thu, 14 Apr 2016 18:20:41 -0400 Subject: [PATCH 3/4] Expand thermistortable to include precomputed slope 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. --- configtool/thermistortablefile.py | 19 +++++++---- temp.c | 56 +++++++++++++++++++------------ 2 files changed, 47 insertions(+), 28 deletions(-) diff --git a/configtool/thermistortablefile.py b/configtool/thermistortablefile.py index e03355a55..9f8e0c294 100644 --- a/configtool/thermistortablefile.py +++ b/configtool/thermistortablefile.py @@ -54,6 +54,7 @@ def generateTempTables(sensors, settings): ofp.output("#define NUMTABLES %d" % len(tl)) ofp.output("#define NUMTEMPS %d" % N) + ofp.output("#define TEMPTABLE_FORMAT 1") ofp.output(""); for i in range(len(tl)): @@ -65,7 +66,7 @@ def generateTempTables(sensors, settings): ofp.close(); return True - ofp.output("const uint16_t PROGMEM temptable[NUMTABLES][NUMTEMPS][2] = {") + ofp.output("const uint16_t PROGMEM temptable[NUMTABLES][NUMTEMPS][3] = {") tcount = 0 for tn in tl: @@ -102,6 +103,7 @@ def BetaTable(ofp, params, names, settings, finalTable): samples = optimizeTempTable(thrm, N, hiadc) + prev = samples[0] for i in samples: t = thrm.temp(i) if t is None: @@ -117,10 +119,12 @@ def BetaTable(ofp, params, names, settings, finalTable): c = " " else: c = "," - ostr = (" {%4s, %5s}%s // %4d C, %6.0f ohms, %0.3f V," - " %0.2f mW") % (i, int(t * 4), c, int(t), int(round(r)), - vTherm, ptherm * 1000) + delta = (t-thrm.temp(prev))/(prev-i) if i!=prev else 0 + ostr = (" {%4s, %5s, %5s}%s // %4d C, %6.0f ohms, %0.3f V," + " %0.2f mW, m=%0.4f") % (i, int(t * 4), int(delta*4*256), c, + int(t), int(round(r)), vTherm, ptherm * 1000, delta) ofp.output(ostr) + prev = i if finalTable: ofp.output(" }") @@ -145,6 +149,7 @@ def SteinhartHartTable(ofp, params, names, settings, finalTable): samples = optimizeTempTable(thrm, N, hiadc) + prev = samples[0] for i in samples: t = thrm.temp(i) if t is None: @@ -157,8 +162,10 @@ def SteinhartHartTable(ofp, params, names, settings, finalTable): c = " " else: c = "," - ofp.output(" {%4d, %5d}%s // %4d C, %6d ohms" % - (i, int(t * 4), c, int(t), int(round(r)))) + delta = (t-thrm.temp(prev))/(prev-i) if i!=prev else 0 + ofp.output(" {%4d, %5d, %5d}%s // %4d C, %6d ohms, m=%0.4f" % + (i, int(t * 4), int(delta*4*256), c, int(t), int(round(r))), delta) + prev = i if finalTable: ofp.output(" }") diff --git a/temp.c b/temp.c index e0162cf5b..659fa39e8 100644 --- a/temp.c +++ b/temp.c @@ -196,28 +196,40 @@ static uint16_t temp_table_lookup(uint16_t temp, uint8_t sensor) { sersendf_P(PSTR("pin:%d Raw ADC:%d table entry: %d"), temp_sensors[sensor].temp_pin, temp, j); - // Wikipedia's example linear interpolation formula. - // y = ((x - x₀)y₁ + (x₁-x)y₀) / (x₁ - x₀) - // y = temp - // x = ADC reading - // x₀= temptable[j-1][0] - // x₁= temptable[j][0] - // y₀= temptable[j-1][1] - // y₁= temptable[j][1] - temp = ( - // ((x - x₀)y₁ - ((uint32_t)temp - pgm_read_word(&(temptable[table_num][j-1][0]))) * - pgm_read_word(&(temptable[table_num][j][1])) - // + - + - // (x₁-x)y₀) - (pgm_read_word(&(temptable[table_num][j][0])) - (uint32_t)temp) * - pgm_read_word(&(temptable[table_num][j - 1][1]))) - // / - / - // (x₁ - x₀) - (pgm_read_word(&(temptable[table_num][j][0])) - - pgm_read_word(&(temptable[table_num][j - 1][0]))); + #if (TEMPTABLE_FORMAT == 0) + // Wikipedia's example linear interpolation formula. + // y = ((x - x₀)y₁ + (x₁-x)y₀) / (x₁ - x₀) + // y = temp + // x = ADC reading + // x₀= temptable[j-1][0] + // x₁= temptable[j][0] + // y₀= temptable[j-1][1] + // y₁= temptable[j][1] + temp = ( + // ((x - x₀)y₁ + ((uint32_t)temp - pgm_read_word(&(temptable[table_num][j-1][0]))) * + pgm_read_word(&(temptable[table_num][j][1])) + // + + + + // (x₁-x)y₀) + (pgm_read_word(&(temptable[table_num][j][0])) - (uint32_t)temp) * + pgm_read_word(&(temptable[table_num][j - 1][1]))) + // / + / + // (x₁ - x₀) + (pgm_read_word(&(temptable[table_num][j][0])) - + pgm_read_word(&(temptable[table_num][j - 1][0]))); + #elif (TEMPTABLE_FORMAT == 1) + // Linear interpolation using pre-computed slope + // y = y₁ - (x-x₁)*d₁ + #define X1 pgm_read_word(&(temptable[table_num][j][0])) + #define Y1 pgm_read_word(&(temptable[table_num][j][1])) + #define D1 pgm_read_word(&(temptable[table_num][j][2])) + + temp = Y1 - ((((int32_t)temp - X1) * D1 + (1<<7)) >> 8); + #else + #error "temptable format unrecognized" + #endif if (DEBUG_PID && (debug_flags & DEBUG_PID)) sersendf_P(PSTR(" temp:%d.%d"), temp / 4, (temp % 4) * 25); From db997b4b77253574435b368e66eed935fa2cb0d6 Mon Sep 17 00:00:00 2001 From: Phil Hord Date: Thu, 14 Apr 2016 18:57:50 -0400 Subject: [PATCH 4/4] Optimize ADC temperature search 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. --- temp.c | 105 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 52 insertions(+), 53 deletions(-) diff --git a/temp.c b/temp.c index 659fa39e8..f43c55cb4 100644 --- a/temp.c +++ b/temp.c @@ -186,65 +186,64 @@ static uint16_t mcp3008_read(uint8_t channel) { */ #if defined TEMP_THERMISTOR || defined TEMP_MCP3008 static uint16_t temp_table_lookup(uint16_t temp, uint8_t sensor) { - uint8_t j; + uint8_t lo, hi; uint8_t table_num = temp_sensors[sensor].additional; - for (j = 1; j < NUMTEMPS; j++) { - if (pgm_read_word(&(temptable[table_num][j][0])) > temp) { - - if (DEBUG_PID && (debug_flags & DEBUG_PID)) - sersendf_P(PSTR("pin:%d Raw ADC:%d table entry: %d"), - temp_sensors[sensor].temp_pin, temp, j); - - #if (TEMPTABLE_FORMAT == 0) - // Wikipedia's example linear interpolation formula. - // y = ((x - x₀)y₁ + (x₁-x)y₀) / (x₁ - x₀) - // y = temp - // x = ADC reading - // x₀= temptable[j-1][0] - // x₁= temptable[j][0] - // y₀= temptable[j-1][1] - // y₁= temptable[j][1] - temp = ( - // ((x - x₀)y₁ - ((uint32_t)temp - pgm_read_word(&(temptable[table_num][j-1][0]))) * - pgm_read_word(&(temptable[table_num][j][1])) - // + - + - // (x₁-x)y₀) - (pgm_read_word(&(temptable[table_num][j][0])) - (uint32_t)temp) * - pgm_read_word(&(temptable[table_num][j - 1][1]))) - // / - / - // (x₁ - x₀) - (pgm_read_word(&(temptable[table_num][j][0])) - - pgm_read_word(&(temptable[table_num][j - 1][0]))); - #elif (TEMPTABLE_FORMAT == 1) - // Linear interpolation using pre-computed slope - // y = y₁ - (x-x₁)*d₁ - #define X1 pgm_read_word(&(temptable[table_num][j][0])) - #define Y1 pgm_read_word(&(temptable[table_num][j][1])) - #define D1 pgm_read_word(&(temptable[table_num][j][2])) - - temp = Y1 - ((((int32_t)temp - X1) * D1 + (1<<7)) >> 8); - #else - #error "temptable format unrecognized" - #endif - - if (DEBUG_PID && (debug_flags & DEBUG_PID)) - sersendf_P(PSTR(" temp:%d.%d"), temp / 4, (temp % 4) * 25); - - // Value found, no need to read the table further. - break; - } + // Binary search for table value bigger than our target + for (lo = 0, hi=NUMTEMPS-1 ; hi-lo > 1 ; ) { + uint8_t j = lo + (hi - lo) / 2 ; + if (pgm_read_word(&(temptable[table_num][j][0])) >= temp) + hi = j ; + else + lo = j ; } + // lo = index of highest entry less than target + // hi = index of lowest entry greater than or equal to target if (DEBUG_PID && (debug_flags & DEBUG_PID)) - sersendf_P(PSTR(" Sensor:%d\n"), sensor); + sersendf_P(PSTR("pin:%d Raw ADC:%d table entry: %d"), + temp_sensors[sensor].temp_pin, temp, hi); + + #if (TEMPTABLE_FORMAT == 0) + // Wikipedia's example linear interpolation formula. + // y = ((x - x₀)y₁ + (x₁-x)y₀) / (x₁ - x₀) + // y = temp + // x = ADC reading + // x₀= temptable[lo][0] + // x₁= temptable[hi][0] + // y₀= temptable[lo][1] + // y₁= temptable[hi][1] + temp = ( + // ((x - x₀)y₁ + ((uint32_t)temp - pgm_read_word(&(temptable[table_num][lo][0]))) * + pgm_read_word(&(temptable[table_num][hi][1])) + // + + + + // (x₁-x)y₀) + (pgm_read_word(&(temptable[table_num][hi][0])) - (uint32_t)temp) * + pgm_read_word(&(temptable[table_num][lo][1]))) + // / + / + // (x₁ - x₀) + (pgm_read_word(&(temptable[table_num][hi][0])) - + pgm_read_word(&(temptable[table_num][lo][0]))); + #elif (TEMPTABLE_FORMAT == 1) + // Linear interpolation using pre-computed slope + // y = y₁ - (x-x₁)*d₁ + #define X1 pgm_read_word(&(temptable[table_num][hi][0])) + #define Y1 pgm_read_word(&(temptable[table_num][hi][1])) + #define D1 pgm_read_word(&(temptable[table_num][hi][2])) + + temp = Y1 - ((((int32_t)temp - X1) * D1 + (1<<7)) >> 8); + #else + #error "temptable format unrecognized" + #endif + + if (DEBUG_PID && (debug_flags & DEBUG_PID)) + sersendf_P(PSTR(" temp:%d.%d"), temp / 4, (temp % 4) * 25); - // Clamp for overflows. - if (j == NUMTEMPS) - temp = temptable[table_num][NUMTEMPS - 1][1]; + if (DEBUG_PID && (debug_flags & DEBUG_PID)) + sersendf_P(PSTR(" Sensor:%d\n"), sensor); return temp; }