Емельянов Эдуард Владимирович (eddy_em) wrote,
Емельянов Эдуард Владимирович
eddy_em

Category:

Кусочно-линейная интерполяция на микроконтроллере

Эта заметка — о применении результатов, полученных в предыдущей. Как обычно, код на гитхабе.

Итак, в октаве все прекрасно считалось, но нужно этот код перенести на микроконтроллер. У бедняг STM32F0 нет даже аппаратного деления, поэтому мучить их флоатами — совсем уж не комильфо! ОК, используем старые добрые цепные дроби. Если функцию rat запускать не саму по себе, а в виде [N, D] = rat(x, prec), то получим числитель и знаменатель дроби, являющейся приближением к заданному числу с точностью prec. Таким образом я сконвертировал коэффициенты в обычные дроби, а температуры умножил на десять, чтобы иметь квант, равный 0.1°C.
Вот такая функция получилась:
/**
 * @brief getNTC - return temperature of NTC (*10 degrC)
 * @param nch - NTC channel number (0..3)
 * @return
 */
int16_t getNTC(int nch){
#define NKNOTS  (9)
    const int16_t ADU[NKNOTS] = {427,   468,  514,  623,  754, 910, 1087, 1295, 1538};
    const int16_t T[NKNOTS]   = {-200, -180, -159, -116,  -72, -26,   23,   75,  132};
    /*
     * coefficients: 0.050477   0.045107   0.039150   0.033639   0.029785   0.027017   0.024996   0.023522   0.022514
     * use
     * [N D] = rat(K*10); printf("%d, ", N); printf("%d, ", D);
     */
    const int16_t N[NKNOTS] = {1377, 295, 258, 110, 291, 77, 1657, 191, 120};
    const int16_t D[NKNOTS] = {2728, 654, 659, 327, 977, 285, 6629, 812, 533};

    if(nch < 0 || nch > 3) return -30000;
    uint16_t val = getADCval(nch);
    // find interval
    int idx = (NKNOTS+1)/2; // middle
    while(idx > 0 && idx < NKNOTS){
        int16_t left = ADU[idx];
        int half = idx / 2;
        if(val < left){
            if(idx == 0) break;
            if(val > ADU[idx-1]){ // found
                --idx;
                break;
            }
            idx = half;
        }else{
            if(idx == NKNOTS - 1) break; // more than max value
            if(val < ADU[idx+1]) break;  // found
            idx += half;
        }
    }
    if(idx < 0) idx = 0;
    else if(idx > NKNOTS-1) idx = NKNOTS - 1;
    // T = Y0(idx) + K(idx) * (ADU - X0(idx));
    int16_t valT = T[idx] + (N[idx]*(val - ADU[idx]))/D[idx];
#undef NKNOTS
    return valT;
}

Все константы замечательно вошли в int16_t, а чтобы быть уверенным, что в возможном диапазоне температур не возникнет "нежданчиков", я прогнал эту функцию на компьютере во всем возможном диапазоне значений.
Для того, чтобы считывать с АЦП более-менее стабильные значения, добавил медианный фильтр:
/**
 * @brief getADCval - calculate median value for `nch` channel
 * @param nch - number of channel
 * @return
 */
uint16_t getADCval(int nch){
    int i, addr = nch;
    register uint16_t temp;
#define PIX_SORT(a,b) { if ((a)>(b)) PIX_SWAP((a),(b)); }
#define PIX_SWAP(a,b) { temp=(a);(a)=(b);(b)=temp; }
    uint16_t p[9];
    for(i = 0; i < 9; ++i, addr += NUMBER_OF_ADC_CHANNELS) // first we should prepare array for optmed
        p[i] = ADC_array[addr];
    PIX_SORT(p[1], p[2]) ; PIX_SORT(p[4], p[5]) ; PIX_SORT(p[7], p[8]) ;
    PIX_SORT(p[0], p[1]) ; PIX_SORT(p[3], p[4]) ; PIX_SORT(p[6], p[7]) ;
    PIX_SORT(p[1], p[2]) ; PIX_SORT(p[4], p[5]) ; PIX_SORT(p[7], p[8]) ;
    PIX_SORT(p[0], p[3]) ; PIX_SORT(p[5], p[8]) ; PIX_SORT(p[4], p[7]) ;
    PIX_SORT(p[3], p[6]) ; PIX_SORT(p[1], p[4]) ; PIX_SORT(p[2], p[5]) ;
    PIX_SORT(p[4], p[7]) ; PIX_SORT(p[4], p[2]) ; PIX_SORT(p[6], p[4]) ;
    PIX_SORT(p[4], p[2]) ;
    return p[4];
#undef PIX_SORT
#undef PIX_SWAP
}

Теперь DMA девятикратно записывает данные в буфер, а когда необходимо получить значение измерений конкретного канала АЦП, данные "выуживаются" в отдельный буфер и из него уже вычисляется медиана.

Получилось вполне прилично. Показания терморезистора 1094.8 Ом → температура 25.0°C. Четыре канала NTC показывают: NTC0=247, NTC1=249, NTC2=251, NTC3=253. Т.е. вполне себе 25±0.5°C.
Благодаря медианной фильтрации данные не скачут как бешеные. Вот такой простой строчкой:
echo "[?]" > /dev/ttyUSB0; for x in $(seq 1 10); do echo "[T0]" > /dev/ttyUSB0; sleep 1; done

можно с интервалом в 1с опрашивать нулевой канал. На выходе стабильные 249 и иногда проскакивает 250. У первого канала 251/252, у второго — 253 (иногда 254) и у третьего — 255 (изредка 256). TRD показывает 25.2°C.
Азот уже утащили, поэтому для теста около нуля по Цельсию я поместил ненадолго банку с тосолом в морозилку. TRD показывает +4.7°C, все 4 канала показывают 5.0. TRD показывает 4.1°C, на NTC 3.8..4.1!
В общем, годится!!!
Tags: c, stm32, аппроксимация, железяки, термодатчики
Subscribe

  • Выделение 4-связных компонент на изображении

    Я уже давным-давно писал об этом алгоритме, но когда понадобилось его однозначно и надежно применить, оказалось, что на некоторых тестовых…

  • А что, в С так нельзя?

    Пытаюсь передать в функцию цвет как массив. Функция такая: void Pattern_draw3(Img3 *img, Pattern *p, int xul, int yul, uint8_t colr[3]); И…

  • Ардуиноподход

    Вот как ни гляну, большинство народу для считывания данных калибровки в BMP/BME280 использует типичный ардуиноподход, как, например, здесь: зачем-то…

promo eddy_em august 17, 2019 12:33 3
Buy for 10 tokens
Юра намедни напечатал корпус для хронометра. Для первого блина получилось неплохо: И еще немного фотографий:
  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic
  • 0 comments