2010-09-04 4 views
4

Мне требуется написать функцию, которая использует таблицу поиска для значений АЦП для аналогового входа температурного датчика, и обнаруживает температуру с учетом значения АЦП путем «интерполяции» - линейного приближения. Я создал функцию и написал для нее несколько тестовых примеров, я хочу знать, есть ли что-то, что вы, ребята, можете предложить улучшить код, поскольку это предполагается для встроенного uC, возможно, stm32.Проверка кода встраиваемого кода

Я отправляю свой код и прикрепляю свой файл C, он будет скомпилирован и запущен.

Пожалуйста, дайте мне знать, если у вас есть комментарии/предложения по улучшению.

Я также хочу немного узнать о литье из uint32_t, чтобы плавать, что я делаю, если это эффективный способ кодирования.

#include <windows.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <stdint.h> 

#define TEMP_ADC_TABLE_SIZE 15 

typedef struct 
{ 
int8_t temp;  
uint16_t ADC;  
}Temp_ADC_t; 

const Temp_ADC_t temp_ADC[TEMP_ADC_TABLE_SIZE] = 
{ 
    {-40,880}, {-30,750}, 
    {-20,680}, {-10,595}, 
    {0,500}, {10,450}, 
    {20,410}, {30,396}, 
    {40,390}, {50,386}, 
    {60,375}, {70,360}, 
    {80,340}, {90,325}, 
    {100,310} 
}; 

// This function finds the indices between which the input reading lies. 
// It uses an algorithm that doesn't need to loop through all the values in the 
// table but instead it keeps dividing the table in two half until it finds 
// the indices between which the value is or the exact index. 
// 
// index_low, index_high, are set to the indices if a value is between sample 
// points, otherwise if there is an exact match then index_mid is set. 
// 
// Returns 0 on error, 1 if indices found, 2 if exact index is found. 
uint8_t find_indices(uint16_t ADC_reading, 
        const Temp_ADC_t table[], 
        int8_t dir, 
        uint16_t* index_low, 
        uint16_t* index_high, 
        uint16_t* index_mid, 
        uint16_t table_size) 
{ 
    uint8_t found = 0; 
    uint16_t mid, low, high; 
    low = 0; 
    high = table_size - 1; 

    if((table != NULL) && (table_size > 0) && (index_low != NULL) && 
     (index_mid != NULL) && (index_high != NULL)) 
    { 
     while(found == 0) 
     { 
      mid = (low + high)/2; 

      if(table[mid].ADC == ADC_reading) 
      { 
       // exact match      
       found = 2;    
      } 
      else if(table[mid].ADC < ADC_reading) 
      { 
       if(table[mid + dir].ADC == ADC_reading) 
       { 
        // exact match   
        found = 2; 
        mid = mid + dir;        
       } 
       else if(table[mid + dir].ADC > ADC_reading) 
       { 
        // found the two indices 
        found = 1; 
        low = (dir == 1)? mid : (mid + dir); 
        high = (dir == 1)? (mid + dir) : mid;        
       } 
       else if(table[mid + dir].ADC < ADC_reading) 
       {      
        low = (dir == 1)? (mid + dir) : low; 
        high = (dir == 1) ? high : (mid + dir); 
       }    
      } 
      else if(table[mid].ADC > ADC_reading) 
      { 
       if(table[mid - dir].ADC == ADC_reading) 
       { 
        // exact match   
        found = 2; 
        mid = mid - dir;        
       } 
       else if(table[mid - dir].ADC < ADC_reading) 
       { 
        // found the two indices 
        found = 1; 
        low = (dir == 1)? (mid - dir) : mid; 
        high = (dir == 1)? mid : (mid - dir);        
       } 
       else if(table[mid - dir].ADC > ADC_reading) 
       { 
        low = (dir == 1)? low : (mid - dir); 
        high = (dir == 1) ? (mid - dir) : high; 
       } 
      } 
     }   
     *index_low = low; 
     *index_high = high; 
     *index_mid = mid;   
    } 

    return found; 
} 

// This function uses the lookup table provided as an input argument to find the 
// temperature for a ADC value using linear approximation. 
// 
// Temperature value is set using the temp pointer. 
// 
// Return 0 if an error occured, 1 if an approximate result is calculate, 2 
// if the sample value match is found. 

uint8_t lookup_temp(uint16_t ADC_reading, const Temp_ADC_t table[], 
        uint16_t table_size ,int8_t* temp) 
{ 
    uint16_t mid, low, high; 
    int8_t dir; 
    uint8_t return_code = 1; 
    float gradient, offset; 

    low = 0; 
    high = table_size - 1; 

    if((table != NULL) && (temp != NULL) && (table_size > 0)) 
    { 
     // Check if ADC_reading is out of bound and find if values are 
     // increasing or decreasing along the table. 
     if(table[low].ADC < table[high].ADC) 
     { 
      if(table[low].ADC > ADC_reading) 
      { 
       return_code = 0;          
      } 
      else if(table[high].ADC < ADC_reading) 
      { 
       return_code = 0; 
      } 
      dir = 1; 
     }  
     else 
     { 
      if(table[low].ADC < ADC_reading) 
      { 
       return_code = 0;          
      } 
      else if(table[high].ADC > ADC_reading) 
      { 
       return_code = 0; 
      } 
      dir = -1; 
     } 
    } 
    else 
    { 
     return_code = 0;  
    } 

    // determine the temperature by interpolating 
    if(return_code > 0) 
    { 
     return_code = find_indices(ADC_reading, table, dir, &low, &high, &mid, 
            table_size); 

     if(return_code == 2) 
     { 
      *temp = table[mid].temp; 
     } 
     else if(return_code == 1) 
     { 
      gradient = ((float)(table[high].temp - table[low].temp))/
         ((float)(table[high].ADC - table[low].ADC)); 
      offset = (float)table[low].temp - gradient * table[low].ADC; 
      *temp = (int8_t)(gradient * ADC_reading + offset); 
     } 
    } 

    return return_code; 
} 



int main(int argc, char *argv[]) 
{ 
    int8_t temp = 0; 
    uint8_t x = 0; 
    uint16_t u = 0; 
    uint8_t return_code = 0; 
    uint8_t i; 

    //Print Table 
    printf("Lookup Table:\n"); 
    for(i = 0; i < TEMP_ADC_TABLE_SIZE; i++) 
    { 
     printf("%d,%d\n", temp_ADC[i].temp, temp_ADC[i].ADC);     
    } 

    // Test case 1 
    printf("Test case 1: Find the temperature for ADC Reading of 317\n"); 
    printf("Temperature should be 95 Return Code should be 1\n"); 
    return_code = lookup_temp(317, temp_ADC, TEMP_ADC_TABLE_SIZE, &temp); 
    printf("Temperature: %d C\n", temp); 
    printf("Return code: %d\n\n", return_code); 

    // Test case 2 
    printf("Test case 2: Find the temperature for ADC Reading of 595 (sample value)\n"); 
    printf("Temperature should be -10, Return Code should be 2\n"); 
    return_code = lookup_temp(595, temp_ADC, TEMP_ADC_TABLE_SIZE, &temp); 
    printf("Temperature: %d C\n", temp); 
    printf("Return code: %d\n\n", return_code); 

    // Test case 3 
    printf("Test case 3: Find the temperature for ADC Reading of 900 (out of bound - lower)\n"); 
    printf("Return Code should be 0\n"); 
    return_code = lookup_temp(900, temp_ADC, TEMP_ADC_TABLE_SIZE, &temp); 
    printf("Return code: %d\n\n", return_code); 

    // Test case 4 
    printf("Test case 4: Find the temperature for ADC Reading of 300 (out of bound - Upper)\n"); 
    printf("Return Code should be 0\n"); 
    return_code = lookup_temp(300, temp_ADC, TEMP_ADC_TABLE_SIZE, &temp); 
    printf("Return code: %d\n\n", return_code); 

    // Test case 5 
    printf("Test case 5: NULL pointer (Table pointer) handling\n"); 
    printf("Return Code should be 0\n"); 
    return_code = lookup_temp(595, NULL, TEMP_ADC_TABLE_SIZE, &temp); 
    printf("Return code: %d\n\n", return_code); 

    // Test case 6 
    printf("Test case 6: NULL pointer (temperature result pointer) handling\n"); 
    printf("Return Code should be 0\n"); 
    return_code = lookup_temp(595, temp_ADC, TEMP_ADC_TABLE_SIZE, NULL); 
    printf("Return code: %d\n", return_code); 

    // Test case 7 
    printf("Test case 7: Find the temperature for ADC Reading of 620\n"); 
    printf("Temperature should be -14 Return Code should be 1\n"); 
    return_code = lookup_temp(630, temp_ADC, TEMP_ADC_TABLE_SIZE, &temp); 
    printf("Temperature: %d C\n", temp); 
    printf("Return code: %d\n\n", return_code); 

    // Test case 8 
    printf("Test case 8: Find the temperature for ADC Reading of 880 (First table element test)\n"); 
    printf("Temperature should be -40 Return Code should be 2\n"); 
    return_code = lookup_temp(880, temp_ADC, TEMP_ADC_TABLE_SIZE, &temp); 
    printf("Temperature: %d C\n", temp); 
    printf("Return code: %d\n\n", return_code); 

    // Test case 9 
    printf("Test case 9: Find the temperature for ADC Reading of 310 (Last table element test)\n"); 
    printf("Temperature should be 100 Return Code should be 2\n"); 
    return_code = lookup_temp(310, temp_ADC, TEMP_ADC_TABLE_SIZE, &temp); 
    printf("Temperature: %d C\n", temp); 
    printf("Return code: %d\n\n", return_code); 

    printf("Press ENTER to continue...\n"); 
    getchar(); 
    return 0; 
} 

ответ

2

Существует много возможностей, которые вы можете улучшить. Прежде всего, наилучший целочисленный тип данных зависит от машины (размер слова). Я не знаю, как объявлены ваши int8_t и uint16_t.

Кроме того, не для исполнения, но для удобства чтения, я обычно не использую "каскадный" сослагательное наклонение, как

if condition 
{ 
    if another_condition 
    { 
     if third condition 
     { 

, но вместо этого:

if not condition 
    return false; 

// Here the condition IS true, thus no reason to indent 

Другой точка внимания:

low = (dir == 1)? mid : (mid + dir); 
high = (dir == 1)? (mid + dir) : mid; 

вы делаете dir == 1 дважды, лучше использовать ifs:

int sum = mid+dir; 
if dir == 1 
{ 
low = mid; 
high = sum; 
} 
else 
{ 
low=sum; 
high=mid; 
} 

Но есть еще что сказать. Например, вы можете использовать более быстрый алгоритм поиска.

+1

'uint16_t' и т. Д. Определены в' stdint.h' в C99. Это хорошая практика. –

4

Почему ваш поиск по бисекции должен обрабатывать как восходящие, так и нисходящие таблицы? В любом случае таблица жестко закодирована, поэтому подготовьте таблицу в автономном режиме, и вы уменьшите вдвое сложность. Вам также, как правило, не нужно делать половину ваших сравнений - просто остановитесь, когда высокие и низкие смежные или равные.

В такой малой программе очень мало точек проверки для ненулевой таблицы и других входов и молча сообщают о несоответствии - либо код возврата, либо код ошибки, либо убедитесь, что одно место функция называется недействительной вызывается с недопустимыми указателями (нет оснований предполагать, что недопустимый указатель равен NULL, только потому, что NULL может быть недопустимым указателем на некоторые системы).

без дополнительных сложностей, поиск стал бы что-то вроде:

enum Match { MATCH_ERROR, MATCH_EXACT, MATCH_INTERPOLATE, MATCH_UNDERFLOW, MATCH_OVERFLOW }; 

enum Match find_indices (uint16_t ADC_reading, 
        const Temp_ADC_t table[], 
        uint16_t* index_low, 
        uint16_t* index_high) 
{ 
    uint16_t low = *index_low; 
    uint16_t high = *index_high; 

    if (low >= high) return MATCH_ERROR; 
    if (ADC_reading < table [ low ].ADC) return MATCH_UNDERFLOW; 
    if (ADC_reading > table [ high ].ADC) return MATCH_OVERFLOW; 

    while (low < high - 1) 
    { 
     uint16_t mid = (low + high)/2; 
     uint16_t val = table [ mid ].ADC; 

     if (ADC_reading > val) 
     { 
      low = mid; 
      continue; 
     } 

     if (ADC_reading < val) 
     { 
      high = mid; 
      continue; 
     } 

     low = high = mid; 
     break; 
    } 

    *index_low = low; 
    *index_high = high; 

    if (low == high) 
     return MATCH_EXACT; 
    else 
     return MATCH_INTERPOLATE; 
} 

Поскольку таблица была предварительно подготовлена ​​к возрастанию, и поиск возвращает значимое перечисление, а не целый кода, вы не» т нужно много в lookup_temp:

enum Match lookup_temp (uint16_t ADC_reading, const Temp_ADC_t table[], 
       uint16_t table_size, int8_t* temp) 
{ 
    uint16_t low = 0; 
    uint16_t high = table_size - 1; 

    enum Match match = find_indices (ADC_reading, table, &low, &high); 

    switch (match) { 
     case MATCH_INTERPOLATE: 
      { 
       float gradient = ((float)(table[high].temp - table[low].temp))/
           ((float)(table[high].ADC - table[low].ADC)); 
       float offset = (float)table[low].temp - gradient * table[low].ADC; 

       *temp = (int8_t)(gradient * ADC_reading + offset); 

       break; 
      } 

     case MATCH_EXACT: 
      *temp = table[low].temp; 
      break; 
    } 

    return match; 
} 

Учитывая все условия при расчете градиента 16 бит Интс, вы могли бы выполнить интерполяцию в 32 бита, до тех пор, как вы вычислить все условия числителе, прежде чем деления:

*temp = temp_low + uint16_t ((uint32_t (ADC_reading - adc_low) * uint32_t ( temp_high - temp_low))/uint32_t (adc_high - adc_low)); 
5

Я вообще вычислить поиск таблицы в автономном режиме и исполняемый код сводится к:

 
temp = table[dac_value]; 

Особенно, если идя внедренный, вы не хотите с плавающей точкой, часто не нужно. Предварительная вычисление таблицы также решает эту проблему.

Предварительные вычисления также решают проблему наличия эффективного алгоритма, вы можете быть неаккуратным и медленным, как вы хотите, вам нужно только делать это вычисление редко. Ни один алгоритм не сможет конкурировать с поисковой таблицей во время выполнения. Пока у вас есть место для таблицы поиска, это беспроигрышный. Если вы не имеете, скажем 256 мест в выпускном для 8-битный ЦАП, например, вы можете иметь 128 мест, и вы можете сделать немного реальном времени интерполяции:

 
//TODO add special case for max dac_value and max dac_value-1 or make the table 129 entries deep 
if(dac_value&1) 
{ 
    temp=(table[(dac_value>>1)+0]+table[(dac_value>>1)+1])>>1; 
} 
else 
{ 
    temp=table[dac_value>>1]; 
} 

Я часто нахожу, что таблица кормили в банке и изменится. Твой может быть брошен в камень, но такие же вычисления происходят с откалиброванными устройствами. И вы сделали правильную вещь, проверив, что данные находятся в правильном общем направлении (уменьшается по отношению к dac, увеличиваясь или увеличиваясь по отношению к значениям dac) и, что более важно, проверяйте на деление на ноль. Несмотря на то, что это жестко закодированная таблица, развивают привычки с ожиданием того, что она изменится на другую жесткую кодированную таблицу с желанием не менять каждый интерполяционный код.

Я также считаю, что исходное значение dac является самым важным значением здесь, вычисленная температура может произойти в любое время. Даже если преобразование в градусы некоторого аромата было отлито из камня, неплохо было бы отобразить или сохранить исходное значение dac вместе с вычисленной температурой. Вы всегда можете пересчитать температуру из значения dac, но вы не всегда можете точно воспроизвести необработанное значение dac из вычисленного значения. Это зависит от того, что вы строите естественно, если это термостат для общественного использования в их домах, они не хотят иметь какое-либо шестнадцатеричное значение на дисплее. Но если это какая-то тестовая или инженерная среда, в которой вы собираете данные для последующего анализа или проверки, что какой-то продукт хорош или плох, перенос этого значения dac может быть хорошим. Это займет один или два раза в ситуации, когда инженер, предоставивший вам таблицу, утверждает, что это финальная таблица, а затем меняет ее. Теперь вам нужно вернуться ко всем журналам, которые использовали неверную таблицу, вычислить обратно значение dac с использованием предыдущей таблицы и перекомпилировать temp с помощью новой таблицы и записать новый файл журнала. Если бы у вас было сырое значение dac, и все были обучены мыслить с точки зрения значений dac и что температура была просто ссылкой, вам, возможно, не придется восстанавливать старые значения журнала для каждой новой таблицы калибровки. В худшем случае имеет только температуру в файле журнала и не может определить, какая таблица таблицы была использована для этого файла журнала, файл журнала становится недействительным, если тестируемый блок становится элементом риска и т. Д.

2

Это хорошо что вы включаете тестовую структуру, но ваша тестовая структура не имеет строгости и злоупотребляет принципом DRY (Do not Repeat Yourself).

static const struct test_case 
{ 
    int inval; /* Test reading */ 
    int rcode; /* Expected return code */ 
    int rtemp; /* Expected temperature */ 
} test[] = 
{ 
    { 317, 1, 95 }, 
    { 595, 1, -10 }, 
    { 900, 0, 0 }, // Out of bound - lower 
    { 300, 0, 0 }, // Out of bound - upper 
    { 620, 1, -14 }, 
    { 880, 2, -40 }, // First table element 
    { 310, 2, 100 }, // Last table element 
}; 

Теперь вы можете написать тестовый код для одного теста в функции:

static int test_one(int testnum, const struct test_case *test) 
{ 
    int result = 0; 
    int temp; 
    int code = lookup_temp(test->inval, temp_ADC, TEMP_ADC_TABLE_SIZE, &temp); 

    if (temp == test->rtemp && code == test->rcode) 
     printf("PASS %d: reading %d, code %d, temperature %d\n", 
       testnum, test->inval, code, temp); 
    else 
    { 
     printf("FAIL %d: reading %d, code (got %d, wanted %d), " 
       "temperature (got %d, wanted %d)\n", 
       testnum, test->inval, code, test->rcode, temp, test->rtemp); 
     result = 1; 
    } 
} 

И тогда основная программа может иметь цикл, который приводит в действие функцию тестирования:

#define DIM(x) (sizeof(x)/sizeof(*(x))) 

int failures = 0; 
int i; 

for (i = 0; i < DIM(test); i++) 
    failures += test_one(i + 1, &test[i]); 

if (failures != 0) 
    printf("!! FAIL !! (%d of %d tests failed)\n", failures, (int)DIM(test)); 
else 
    printf("== PASS == (%d tests passed)\n", (int)DIM(test)); 

Теперь, если есть какие-либо проблемы с любым из тестов, будет трудно оправдать, если не выявить проблему. С вашим исходным кодом кто-то мог упустить ошибку.

Очевидно, что если вы хотите получить комментарии о тестах, вы можете добавить в массив const char *tag и предоставить и распечатать те теги. Если вы действительно хотите получить фантазию, вы можете даже закодировать тесты нулевого указателя (например, для включения этих) в режим, включив в массив соответствующие инициализированные указатели - вы можете добавить пару бит-флагов для «table is null» и «указатель температуры - это нуль» и условный код в функции.

Смежные вопросы