Обзор встроенного кода C - PullRequest
4 голосов
/ 04 сентября 2010

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

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

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

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

#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;
}

Ответы [ 4 ]

5 голосов
/ 04 сентября 2010

Я обычно вычисляю таблицу поиска в автономном режиме, а код времени выполнения сводится к:

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, используя предыдущую таблицу, и повторно вычислить temp с использованием новой таблицы и записать новый файл журнала. Если у вас там было необработанное значение ЦАП, и все были обучены думать о значениях ЦАП и о том, что температура была просто справочной, вам, возможно, не придется восстанавливать старые значения журнала для каждой новой таблицы калибровки. В наихудшем случае - наличие только температуры в файле журнала и невозможность определить, какая таблица калибровки использовалась для этого файла журнала, файл журнала становится недействительным, проверенный модуль становится элементом риска и т. Д.

4 голосов
/ 04 сентября 2010

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

В таких небольших программах очень мало проверок на наличие ненулевых таблиц и других входных данных и молча сообщается о несоответствии - либо код возврата и ошибки, либо убедитесь, что в одном месте, где вызывается функция, она не вызывается с неверными указателями (нет оснований предполагать, что недопустимый указатель равен 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 ) );
2 голосов
/ 04 сентября 2010

Хорошо, что вы включили тестовый фреймворк, но вашему тестовому фреймворку не хватает строгости и он нарушает принцип СУХОГО (не повторяйте себя).

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 к массиву, поставить и распечатать эти теги. Если вы действительно хотите получить фантазию, вы можете даже закодировать тесты с нулевым указателем (спасибо за их включение) в режиме, включив в массив соответствующим образом инициализированные указатели - вы можете добавить пару битовых флагов для «таблица равна нулю» 'указатель температуры нулевой' и условный код в функции.

2 голосов
/ 04 сентября 2010

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

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

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;
}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...