3

I have a table to calculate temperature based on a range of ADC (analogue-digital converter) counts that I need to implement in C. I'm not sure how to go about doing so since it won't be an exact value, it will be in an overlapping range.

The code will take in an ADC integer value (columns 2-4 meaning ADC low, ADC center, and ADC high respectively) read from a sensor and spit out the temperature (column 1). These values came from a document which I converted from resistance to voltage to ADC. The ADC value will be a whole number and it needs to be the best fit within the range, which I assume would mean closest to the center value. It doesn't need to be super exact since it's a pretty stable value (usually between 370-350 aka 25-26C) and it will be used to determine overheating (41C).

Here's an example of a few cells of the table:

Temperature  | ADC Low     |  ADC Center |  ADC High
-------------+-------------+-------------+------------
          25 | 362.1804923 | 372.3636364 | 382.4913871
          26 | 349.9452011 | 359.9395371 | 369.8908548
          27 | 338.1432261 | 347.9502029 | 357.7197732
          28 | 326.7557813 | 336.3737597 | 345.9668118
          29 | 315.7666703 | 325.2012277 | 334.6164426
          30 | 305.1694416 | 314.4195099 | 323.6592884
          31 | 294.9747625 | 304.0429113 | 313.1063265

Any help would be appreciated.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
smd
  • 55
  • 1
  • 3
  • 9
  • Do you really mean you must have a lookup table? No other alternative search algorithms? – kaylum Nov 30 '16 at 04:25
  • Are you wondering what data structure to use to store the lookup table, or how to implement a lookup method that takes in a current reading (in amperes) and gives the temperature? – Greg Schmit Nov 30 '16 at 04:27
  • So, if super-accuracy isn't an issue, you could calculate the mid-point of the ADC centre values between 25 and 26, between 26 and 27, … and round that to the nearest integer. When the input integer is read, you can find the temparature that matches because you have the boundary value where higher numbers are 25ºC, then 26ºC, then 27ºC, etc. I suppose you could get fancy and play with Fuzzy Logic, treating the 3 numbers as a triangle, and see how much the input is 25ºC and how much 26ºC and how much 27ºC and then come up with a composite temperature, but it's unlikely to be worth it. – Jonathan Leffler Nov 30 '16 at 04:44
  • The lookup table was suggested/is preferred. Yes, I'm wondering about data structure (not reading in the file, but putting it as a structure in the code) then using the value to determine the temperature – smd Nov 30 '16 at 05:12

2 Answers2

3

Here's some workable code. It assumes the mapping table is built-in; if you need to read it dynamically, that requires some revised code but isn't otherwise a fundamental problem.

As outlined in a comment, the code determines an integer ADC value for each temperature such that if the value is greater than the limit, the temperature is the given value, using linear interpolation between the ADC central value entries in the raw ADC data table. The search for the relevant temperature is a simple linear search. You could use a binary search if you wanted, or create a table for plausible ADC values and map each ADC value to a temperature (uses more space, but gives the fastest lookup). But for a range of 25-41ºC, it is barely worth worrying about the performance of the linear search unless you can demonstrate that it is a dramatic bottleneck, especially as the normal search will only need to look at 2 or 3 entries at the beginning of the list (so the normal linear search might well out-perform binary search!).

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

struct raw_adc
{
    int temp;
    double adc_lo;
    double adc_mid;
    double adc_hi;
};

/* Assumed sorted in order of increasing temperature */
/* Assumed monotonic in order of adc_mid values in sorted order */
/* Assumed lower temperature has higher adc_mid value */
/* Assumed ADC readings always positive (0 used as dummy) */
/* Assume contiguous series of temperatures */
static struct raw_adc raw_adc_data[] =
{
    { 25, 362.1804923, 372.3636364, 382.4913871 },
    { 26, 349.9452011, 359.9395371, 369.8908548 },
    { 27, 338.1432261, 347.9502029, 357.7197732 },
    { 28, 326.7557813, 336.3737597, 345.9668118 },
    { 29, 315.7666703, 325.2012277, 334.6164426 },
    { 30, 305.1694416, 314.4195099, 323.6592884 },
    { 31, 294.9747625, 304.0429113, 313.1063265 },
};
enum { NUM_RAW_ADC = sizeof(raw_adc_data) / sizeof(raw_adc_data[0]) };

struct map_adc
{
    int temp;
    int adc_value;
};

static struct map_adc adc_map[NUM_RAW_ADC];

static void map_raw_adc_values(void)
{
    int i;
    for (i = 0; i < NUM_RAW_ADC - 1; i++)
    {
        adc_map[i].temp = raw_adc_data[i].temp;
        /* Optionally add 0.5 before assigning */
        adc_map[i].adc_value = (raw_adc_data[i].adc_mid + raw_adc_data[i+1].adc_mid) / 2;
    }
    /* Last value is deemed to be hotter than the last recorded value */
    adc_map[i].temp = adc_map[i-1].temp + 1;
    adc_map[i].adc_value = 0;
}

static int temp_from_adc(int adc_value)
{
    int i;
    for (i = 0; i < NUM_RAW_ADC; i++)
    {
        /* Use of > determined by inspection of data - colder temps have higher ADC value */
        if (adc_value > adc_map[i].adc_value)
            return adc_map[i].temp;
    }
    assert(0);      /* Can't happen! */
    return 300;     /* If it gets here, the machine is too hot! */
}

static void dump_adc_map(void)
{
    for (int i = 0; i < NUM_RAW_ADC; i++)
    {
        assert(raw_adc_data[i].temp == adc_map[i].temp);
        printf("T = %.3dºC ADC = (%6.2f:%6.2f:%6.2f) Limit = %d\n",
                raw_adc_data[i].temp, raw_adc_data[i].adc_lo,
                raw_adc_data[i].adc_mid, raw_adc_data[i].adc_hi,
                adc_map[i].adc_value);
    }
}

int main(void)
{
    map_raw_adc_values();
    dump_adc_map();
    srand(time(0));
    for (int i = 0; i < 20; i++)
    {
        /* Range of ADC values in table is 294-382 */
        /* Generate random value in that range */
        int adc_rdg = rand() % (382 - 294) + 294;
        printf("ADC: %d = %d ºC\n", adc_rdg, temp_from_adc(adc_rdg));
    }
    return 0;
}

Example run:

T = 025ºC ADC = (362.18:372.36:382.49) Limit = 366
T = 026ºC ADC = (349.95:359.94:369.89) Limit = 353
T = 027ºC ADC = (338.14:347.95:357.72) Limit = 342
T = 028ºC ADC = (326.76:336.37:345.97) Limit = 330
T = 029ºC ADC = (315.77:325.20:334.62) Limit = 319
T = 030ºC ADC = (305.17:314.42:323.66) Limit = 309
T = 031ºC ADC = (294.97:304.04:313.11) Limit = 0
ADC: 298 = 31 ºC
ADC: 358 = 26 ºC
ADC: 343 = 27 ºC
ADC: 315 = 30 ºC
ADC: 358 = 26 ºC
ADC: 352 = 27 ºC
ADC: 374 = 25 ºC
ADC: 322 = 29 ºC
ADC: 372 = 25 ºC
ADC: 376 = 25 ºC
ADC: 333 = 28 ºC
ADC: 334 = 28 ºC
ADC: 356 = 26 ºC
ADC: 307 = 31 ºC
ADC: 304 = 31 ºC
ADC: 305 = 31 ºC
ADC: 324 = 29 ºC
ADC: 358 = 26 ºC
ADC: 324 = 29 ºC
ADC: 322 = 29 ºC
Community
  • 1
  • 1
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • if the table went from -40 to 125, would you recommend a different search method for speed or do you think this would suffice as well? – smd Nov 30 '16 at 05:35
  • It would depend on whether you're under CPU usage pressure or not. At worst, you'd be looking at less than 170 comparisons for a temperature of 125 or more with a linear search; that would be 9 comparisons for a binary search (but compare 16 and 4 for linear vs binary over the range 25-41). So yes, I'd probably use binary search over the much larger temperature range, but that comes with some extra complexity. I have doubts that this is going to stress any system, though. Having said that, one other issue would be accessing memory — page faults, etc. It depends on context! – Jonathan Leffler Nov 30 '16 at 05:40
  • I would probably not keep the 'raw ADC data' table in memory — I'd precompute it. Even if you needed to be able to recalibrate the ADC, I'd probably have the operational program read the ADC map table from file at startup and when requested, and have a program to precompute (re-precompute) the ADC map. Presumably, such recalibrations would be rare. – Jonathan Leffler Nov 30 '16 at 05:44
  • Also, note that you'd need a custom binary search function. The standard one ([`bsearch()`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/bsearch.html)) looks for an exact match, but you need to be able to return a value when the ADC reading does not appear in the table. (I'll also mention that the last 4 of the 6 fractional digits in the raw ADC table are unlikely to be relevant — hence the print format chosen. I have reservations about any of the fractional digits, but definitely the last 4 of them — especially as the ADC only reports integer values.) – Jonathan Leffler Nov 30 '16 at 05:48
0

I'm thinking: use binary-search to traverse an array of structs that declare the range:

struct Range {
    float low;
    float center;
    float high;
    int   temperature;
};

struct RangeTable {
    struct Range* list;
    size_t length;
}

// TODO: Read the file here

struct RangeTable* table = ...
// NOTE: Ensure table.list is sorted in ascending `center` value, either in the file, or use a sorting function on the array after loading.

/////////////

/// <summary>Looks up the <paramref="adcReading" /> value in the provided table and returns a pointer to the best-matching Range struct value.</summary>
struct Range* lookup( struct RangeTable * table, float adcReading ) {

    // do a binary-search on the `center` value, which will return the Range object with the closest `center` value.
    struct Range* closestCenter = binarySearch( table->list, table->length, adcReading );

    if( closestCenter == NULL ) {
        // error condition.
        exit( 1 );
    }
    if( adcReading < closestCenter->low || adcReading > closestCenter->high ) {
        // out of range
    }
    else {
        // inside the range
    }
}

Implementing the sorting and binary-search functions is up to you, of course.

Dai
  • 141,631
  • 28
  • 261
  • 374