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.

Note by Traumflug:

According to @Wurstnase's performance measurements, gain is
actually pretty huge. Temp conversions 0..1024 before this
and the previous few commits:

  minimum: 1011.84 clock cycles.
  maximum: 1993.92 clock cycles.
  average: 1768.85 clock cycles.

Now:

  minimum: 437.72 clock cycles.
  maximum: 494.76 clock cycles.
  average: 470.278 clock cycles.

That's a speedup by factor 3.7 on average!
This commit is contained in:
Phil Hord 2016-04-14 18:57:50 -04:00 committed by Markus Hitter
parent 65857b17dc
commit fbe1832467
1 changed files with 60 additions and 60 deletions

120
temp.c
View File

@ -186,72 +186,72 @@ 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 (sizeof(temptable[0][0]) == 2 * sizeof(uint16_t)) {
/**
This code handles temptables with value pairs and is deprecated.
It's kept for compatibility with legacy, handcrafted tables, only.
The new code expects tables with triples, nevertheless it's smaller
and also faster. Configtool was already changed to create tables
with triples, only.
*/
// 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])));
} else
if (sizeof(temptable[0][0]) == 3 * sizeof(uint16_t)) {
// 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);
}
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.
//
// lo = index of highest entry less than target.
// hi = index of lowest entry greater than or equal to 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 ;
}
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);
// Clamp for overflows.
if (j == NUMTEMPS)
temp = temptable[table_num][NUMTEMPS - 1][1];
if (sizeof(temptable[0][0]) == 2 * sizeof(uint16_t)) {
/**
This code handles temptables with value pairs and is deprecated.
It's kept for compatibility with legacy, handcrafted tables, only.
The new code expects tables with triples, nevertheless it's smaller
and also faster. Configtool was already changed to create tables
with triples, only.
*/
// 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])));
} else
if (sizeof(temptable[0][0]) == 3 * sizeof(uint16_t)) {
// 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);
}
if (DEBUG_PID && (debug_flags & DEBUG_PID))
sersendf_P(PSTR(" temp:%d.%d"), temp / 4, (temp % 4) * 25);
if (DEBUG_PID && (debug_flags & DEBUG_PID))
sersendf_P(PSTR(" Sensor:%d\n"), sensor);
return temp;
}