Проблема с массивом + последовательным монитором

Я написал эскиз Arduino, который преобразует аналоговое напряжение в температуру с помощью справочной таблицы — код ниже. Недавно я изменил свой LUT, чтобы включить больше значений, но с тех пор мой последовательный монитор не распечатывает мои инструкции отладки и т. д. Поэтому я ограничил количество элементов в массиве, и все вернулось в норму. Но когда я ввожу больше операторов отладки с помощью последовательного монитора, перед моими операторами появляется мусор (хотя он все еще читается, когда я сам его анализирую) или в начале ничего не печатается. Я проверил, что обе скорости передачи данных одинаковы, но мне кажется странным, что если я вынесу некоторые из своих операторов println(), все будет работать нормально. Все остальное протестировал на примере «Hello World», но в простых примерах монитор работает нормально.

 /*
#####ОПИСАНИЕ ПРОЕКТА#####
Измеряет температуру с помощью Arduino и термистора NTC (10 кОм при 25 °C) в цепи делителя напряжения.
Поскольку ответ нелинейный, используется справочная таблица, поскольку существует нелинейный ответ.
Значения в LUT представляют собой прогнозируемые значения АЦП при температуре от 6 ^ C до 40 ^ C.
LUT получен на основе калибровочных тестов.
*/
    //#####КОНСТАНТЫ#####
    const int constDelay = 3000; //постоянная задержка для программы
    const int constNoOfAnaloguePins = 2; //количество аналоговых выводов, которые необходимо прочитать
    const int errReturn = 998; //возвращаемое значение ошибки ***ПРОВЕРЬТЕ СОГЛАСОВАННОСТЬ ЗНАЧЕНИЙ В ФУНКЦИЯХ***
    //#####ОБЪЯВЛЕНИЯ ПЕРЕМЕННЫХ#####
    float avgTempC; // переменная с плавающей запятой для хранения среднего значения нескольких показаний температуры

    //#####ПРОЦЕДУРА НАСТРОЙКИ#####
    void setup()
    {
      //это часть вашего проекта, посвященная настройке — это код, который запускается только ОДИН РАЗ при запуске
      for (int analoguePinCounter = 0; analoguePinCounter < constNoOfAnaloguePins; analoguePinCounter++) //цикл for для установки множества аналоговых выводов
      {
        pinMode(analoguePinCounter, INPUT);  //устанавливаем режим вывода аналогового термисторного входа
      }//конец для
      analogReference(INTERNAL); //используем интервал напряжения 1,1В для разрешения АЦП
      Serial.begin(9600); //скорость передачи данных последовательного монитора
    }//завершаем функцию настройки

    //#####ПРОЦЕДУРА#####
    void loop()
    {
      //это основная часть вашего проекта — сюда помещаем весь постоянно работающий код
      for (int analoguePinCounter = 0; analoguePinCounter < constNoOfAnaloguePins; analoguePinCounter++) //цикл for для снятия показаний температуры с каждого аналогового контакта
      {
        //получить среднее значение LPF по 25 выборкам
        avgTempC = LPF(analoguePinCounter, 25);
        // распечатываем показания
        if (avgTempC < float(errReturn)) // не было никаких значений температуры за пределами диапазона ***ПРОВЕРЬТЕ ЗНАЧЕНИЕ, ВОЗВРАЩЕННОЕ ИЗ LPF, ЕСЛИ ОШИБКА***
        {
          Serial.print(analoguePinCounter);
          Serial.print(" | Average Temperature Readings = ");
          Serial.print(avgTempC, 2);
          Serial.println(" degC.");
          Serial.println("-------------------------");
        }
        else //avgTempC вернул X, указывающий на ошибку
        {
          Serial.print(analoguePinCounter);
          Serial.println("**ERROR IN READING AVERAGE TEMP**");
          Serial.println("-------------------------");
          delay(10);
        }//конец, если
        delay(constDelay/2); //подождите X/2 секунды, пока не перейдём к следующему датчику
      }//конец для
      delay(constDelay); //ждем X секунд до следующего цикла
    }//завершение функции цикла

    /*
    #####LOW PASS FILTER DESCRIPTION#####
    This function is a LPF for smoothing out signals or averaging
    It uses a static buffer to store the last "n" readings, and each time it is called,
    the oldest reading is discarded, the new reading is added, and the stack average
    value is returned.
    The buffer size is user-adjustable, but is constrained between 2 and 50
    An initialization function fills the entire buffer with the input value. 
    This is useful when the LPF function is called for the first time.
    Note: To meet the definition of a true LPF, this function must be called         at regular intervals.
    */
    //#####ФУНКЦИЯ ФИЛЬТРА НИЗКИХ ПРОХОДОВ#####
    float LPF(int pinAnalogue, int bufferSize)
    {
      //##Предпроцессор##
      #define bufferCap 75 //максимальная емкость буфера
      //##КОНСТАНТЫ##
      const int constMAXERRORS = 5; //максимальное количество ошибок, которые могут возникнуть
      const int errReturn = 998; //возврат значения, если есть ошибка
      //##ПЕРЕМЫЕ##
      float tempC; //var для хранения мгновенной температуры
      static float buffer[bufferCap]; //массив, который будет действовать как «стек» временных значений ***НУЖНО СЕЙЧАС ОН ДОЛЖЕН БЫТЬ СТАТИЧЕСКИМ?***
      float tempSum; //для хранения суммы температур
      float output; //возвращаемое значение ФНЧ, которое является средним значением температуры
      int errCounter = 0; //инициализируем счетчик ошибок равным 0

      //убедимся, что размер буфера находится в пределах диапазона
      bufferSize = constrain(bufferSize, 2, bufferCap);

      for (int i=0; i<bufferSize; i++) //цикл for для сохранения временных значений в буферном массиве
      {
        tempC = getTempFloat(pinAnalogue); //вызов функции для получения температуры от контакта
        if ((tempC > -errReturn) && (tempC < errReturn)) //нет ошибки
        {
          buffer[i] = tempC; //сохраняем значение температуры в массив
        }
        else //ошибка
        {
          errCounter++; //приращение счетчика количества ошибочных показаний
          i--; //уменьшаем счетчик, чтобы не пропускать индекс выборки
          if (errCounter > constMAXERRORS)
          {
            return(errReturn+1.0); // возвращаем значение ошибки + 1
          }//конец, если
        }//конец, если
        delay(25); //позволяем АЦП рассчитаться
      }//конец для

      //вычисляем текущее среднее значение стека
      tempSum = 0; //инициализируем итоговую сумму
      for (int i=0; i<bufferSize; i++) //проходим через массив стека
      {
        tempSum = tempSum + buffer[i]; //суммируем показания
      }//конец для

      //средняя сумма
      output = tempSum/bufferSize;

      return(output);
    }//завершаем функцию ФНЧ

    /*
    #####GET TEMP FLOAT DESCRIPTION#####  
    This function converts a thermistor reading into a corresponding temp in ^C
    The thermistor is incorporated into a Voltage Divider Circuit:

       +Vref---[Thermistor]---+--[1.8K]---GND
                              |
                             ADC @ thermPin

    ADC Values were externally calculated from the calibration table using:           ADC = 1023*10000/(Rtherm+10000)
    The LUT is an array of integer constants containing the predicted ADC         values for all temperatures between +6^C to +40.5^C.
    The array index starts at zero, which corresponds to a temperature of         +6^C.
    A linear interpolation between the two closest entries is performed to         give a finer output resolution.
    */

    float getTempFloat (int thermPin)
    {
      //##КОНСТАНТЫ##
      /*1K8*/
      const int constLUTArraySize = 431;
      const int LUT_Therm[constLUTArraySize] = //LUT, содержащий значения АЦП
      {
        223, 224, 225, 226, 227, 228, 229, 230, 231, 232,         //от 7^C до 7,9^C
        233, 234, 235, 236, 237, 238, 239, 240, 241, 242,
        244, 245, 246, 247, 248, 249, 250, 251, 252, 253,
        255, 256, 257, 258, 259, 260, 261, 263, 264, 265,
        266, 267, 268, 269, 271, 272, 273, 274, 275, 277,
        278, 279, 280, 281, 283, 284, 285, 286, 287, 289,
        290, 291, 292, 294, 295, 296, 297, 299, 300, 301,           // от 13 ^C до 13,9 ^C
        302, 304, 305, 306, 308, 309, 310, 312, 313, 314,
        315, 317, 318, 319, 321, 322, 323, 325, 326, 327,
        329, 330, 332, 333, 334, 336, 337, 338, 340, 341,
        343, 344, 345, 347, 348, 350, 351, 353, 354, 355,
        357, 358, 360, 361, 363, 364, 366, 367, 369, 370,
        371, 373, 374, 376, 377, 379, 380, 382, 383, 385,
        387, 388, 390, 391, 393, 394, 396, 397, 399, 400,
        402, 404, 405, 407, 408, 410, 411, 413, 415, 416,
        418, 419, 421, 423, 424, 426, 428, 429, 431, 433,
        434, 436, 438, 439, 441, 443, 444, 446, 448, 449,
        451, 453, 454, 456, 458, 460, 461, 463, 465, 466,
        468, 470, 472, 473, 475, 477, 479, 480, 482, 484,
        486, 488, 489, 491, 493, 495, 497, 498, 500, 502,
        504, 506, 507, 509, 511, 513, 515, 517, 519, 520,
        522, 524, 526, 528, 530, 532, 534, 535, 537, 539,
        541, 543, 545, 547, 549, 551, 553, 555, 557, 559,
        560, 562, 564, 566, 568, 570, 572, 574, 576, 578, 
        580, 582, 584, 586, 588, 590, 592, 594, 596, 598,
        600, 602, 604, 606, 608, 611, 613, 615, 617, 619,
        621, 623, 625, 627, 629, 631, 633, 635, 638, 640,
        642, 644, 646, 648, 650, 652, 655, 657, 659, 661,
        663, 665, 667, 670, 672, 674, 676, 678, 680, 683,
        685, 687, 689, 691, 694, 696, 698, 700, 702, 705,
        707, 709, 711, 714, 716, 718, 720, 723, 725, 727,
        729, 732, 734, 736, 738, 741, 743, 745, 747, 750, 
        752, 754, 757, 759, 761, 764, 766, 768, 771, 773,
        775, 778, 780, 782, 785, 787, 789, 792, 794, 796,
        799, 801, 803, 806, 808, 811, 813, 815, 818, 820,
        822, 825, 827, 830, 832, 834, 837, 839, 842, 844,
        847, 849, 851, 854, 856, 859, 861, 864, 866, 868,
        871, 873, 876, 878, 881, 883, 886, 888, 891, 893,
        896, 898, 901, 903, 906, 908, 911, 913, 916, 918, 
        921, 923, 926, 928, 931, 933, 936, 938, 941, 943,
        946, 948, 951, 953, 956, 958, 961, 963, 966, 969,
        971, 974, 976, 979, 981, 984, 987, 989, 992, 994,
        997, 999, 1002, 1005, 1007, 1010, 1012, 1015, 1017, 1020, //от 40°С до 49,9°С.
        1023                                                     //50^С
      };
      const int errReturn = 998; //значение ошибки, которое возвращается
      //##ПЕРЕМЫЕ##
      float _tempC; //промежуточные результаты + окончательное возвращаемое значение температуры
      int ADC_Lo; //нижнее значение соответствия АЦП
      int ADC_Hi; //более высокое значение соответствия АЦП
      float Temp_Lo; //нижнее число, соответствующее температуре
      int mapTemp_Lo; //нижнее число, которое будет введено в функцию карты
      float Temp_Hi; //более высокое число соответствует температуре
      int mapTemp_Hi; //большее число, которое будет введено в функцию карты

      //подготавливаем АЦП к этому конкретному аналоговому выводу, чтобы разобраться с мультиплексированием и т. д.
      analogRead(thermPin); 
      delay(10);
      // получаем необработанное значение АЦП из VDR
      int thermValue = analogRead(thermPin); 
      //Serial.println(thermValue);
      /*Serial.print("PinNo : ");
      Serial.println(thermPin);*/
      Serial.print("ADC : ");
      Serial.println(thermValue);
      //возвращаем фиктивное значение, если показания датчика выходят за пределы LUT
      if (thermValue < LUT_Therm[0]) //меньше наименьшего значения АЦП
        _tempC = -errReturn-1; // фиктивное значение ниже диапазона
      else if (thermValue > LUT_Therm[constLUTArraySize-1]) //больше максимального значения АЦП
        _tempC = errReturn+1; // фиктивное значение превышения диапазона
      else //значение попадает в диапазон LUT
      {
        for (int i=0; i<constLUTArraySize; i++) //проходим через LUT и ищем совпадение
        {    
          if (LUT_Therm[i] > thermValue) //Значение LUT больше показания
          {  
            //находим ближайшее более высокое значение АЦП
            ADC_Hi = LUT_Therm[i];
            //записываем ближайшую более высокую температуру
            Temp_Hi = float(i/10) + 13.0; //конвертируем во временную
            //получим ближайшую нижнюю температуру - с учетом нижней границы таблицы
            if (i != 0) //общий случай -> пока это не первая запись
            {
              ADC_Lo = LUT_Therm[i-1]; //сохраняем предыдущий элемент массива как нижний
              Temp_Lo = float(i/10) + 13.0 - 0.1; //конвертируем во временную
            }
            else //особый случай -> счетчик = 0, т.е. первая запись массива
            {
              ADC_Lo = LUT_Therm[i]; //сохраняем первый элемент массива
              Temp_Lo = i - float(i/10) + 13.0; //конвертируем во временную
            }//конец, если
            //интерполируем значение температуры для большей точности
            //Обратите внимание, что функция карты не использует математические вычисления с плавающей запятой, поэтому целочисленные значения temp умножаются на 100, а затем результат впоследствии делится на 100
            mapTemp_Lo = Temp_Lo*100;
            mapTemp_Hi = Temp_Hi*100;
            _tempC = float(map(thermValue,ADC_Lo,ADC_Hi,mapTemp_Lo,mapTemp_Hi))/100;
            break;  //выходим из цикла после обнаружения совпадения
          }//конец, если
        }//конец для
      }//конец, если
      return(_tempC);
    }//завершаем функцию getTempFloat

В этом конкретном эскизе на последовательном мониторе не видно вывода. Кто-нибудь сталкивался с этим раньше или есть идеи, как это исправить?

, 👍5

Обсуждение

Попробуйте поместить данные для LUT_Therm в программную память или как-нибудь сжать их., @BrettAM

...потому что похоже, что у вас закончилось место для хранения., @CharlieHanson


3 ответа


Лучший ответ:

4

Используйте макрос F() для всех печатаемых строк в двойных кавычках. Измените строки следующим образом:

Serial.println(" degC.");

...на это:

Serial.println( F(" degC.") );

Это сэкономит около 100 байт оперативной памяти.

И, как предложил БреттАМ, поместите эту таблицу в PROGMEM:

  const int LUT_Therm[constLUTArraySize] PROGMEM = //LUT, содержащий значения АЦП
  {
    223, 224, 225, 226, 227, 228, 229, 230, 231, 232,         //от 7^C до 7,9^C

Для этого необходимо изменить способ доступа к массиву, а именно:

  if (thermValue < LUT_Therm[0]) //меньше наименьшего значения АЦП

...на это:

  if (thermValue < (int) pgm_read_word( &LUT_Therm[0] )) //меньше наименьшего значения АЦП

Обратите внимание на приведение типов, вызов функции и амперсанд вокруг нужного элемента массива. Вы должны заменить все использования массива этой последовательностью. Вы также можете прочитать его один раз в локальный int и затем использовать его как обычно:

      int lut_therm = (int) pgm_read_word(LUT_Therm[i]); // читаем его в

      if (lut_therm > thermValue) //значение LUT больше показания

Это экономит почти 1000 байт оперативной памяти! Сообщаемый размер двоичного эскиза увеличивается на ту же величину[1].

Мне пришлось вытащить таблицу изнутри getTempFloat в область действия файла. Вот полный эскиз с этими модами:

/*
#####ОПИСАНИЕ ПРОЕКТА#####
Измеряет температуру с помощью Arduino и термистора NTC (10 кОм при 25 °C) в цепи делителя напряжения.
Поскольку ответ нелинейный, используется справочная таблица, поскольку существует нелинейный ответ.
Значения в LUT представляют собой прогнозируемые значения АЦП при температуре от 6 ^ C до 40 ^ C.
LUT получен на основе калибровочных тестов.
*/

//#####КОНСТАНТЫ#####
const int constDelay = 3000; //постоянная задержка для программы
const int constNoOfAnaloguePins = 2; //количество аналоговых выводов, которые необходимо прочитать
const int errReturn = 998; //возвращаемое значение ошибки ***ПРОВЕРЬТЕ СОГЛАСОВАННОСТЬ ЗНАЧЕНИЙ В ФУНКЦИЯХ***

//#####ОБЪЯВЛЕНИЯ ПЕРЕМЕННЫХ#####
float avgTempC; // переменная с плавающей запятой для хранения среднего значения нескольких показаний температуры

//#####ПРОЦЕДУРА НАСТРОЙКИ#####
void setup()
{
  //это часть вашего проекта, посвященная настройке — это код, который запускается только ОДИН РАЗ при запуске
  for (int analoguePinCounter = 0; analoguePinCounter < constNoOfAnaloguePins; analoguePinCounter++) //цикл for для установки множества аналоговых выводов
  {
    pinMode(analoguePinCounter, INPUT);  //устанавливаем режим вывода аналогового термисторного входа
  }//конец для
  analogReference(INTERNAL); //используем интервал напряжения 1,1В для разрешения АЦП
  Serial.begin(9600); //скорость передачи данных последовательного монитора
}//завершаем функцию настройки

//#####ПРОЦЕДУРА#####
void loop()
{
  //это основная часть вашего проекта — сюда помещаем весь постоянно работающий код
  for (int analoguePinCounter = 0; analoguePinCounter < constNoOfAnaloguePins; analoguePinCounter++) //цикл for для снятия показаний температуры с каждого аналогового контакта
  {
    //получить среднее значение LPF по 25 выборкам
    avgTempC = LPF(analoguePinCounter, 25);
    // распечатываем показания
    if (avgTempC < float(errReturn)) // не было никаких значений температуры за пределами диапазона ***ПРОВЕРЬТЕ ЗНАЧЕНИЕ, ВОЗВРАЩЕННОЕ ИЗ LPF, ЕСЛИ ОШИБКА***
    {
      Serial.print(analoguePinCounter);
      Serial.print( F(" | Average Temperature Readings = ") );
      Serial.print(avgTempC, 2);
      Serial.println( F(" degC.") );
      Serial.println( F("-------------------------") );
    }
    else //avgTempC вернул X, указывающий на ошибку
    {
      Serial.print(analoguePinCounter);
      Serial.println( F("**ERROR IN READING AVERAGE TEMP**") );
      Serial.println( F("-------------------------") );
      delay(10);
    }//конец, если
    delay(constDelay/2); //подождите X/2 секунды, пока не перейдём к следующему датчику
  }//конец для
  delay(constDelay); //ждем X секунд до следующего цикла
}//завершение функции цикла

/*
#####LOW PASS FILTER DESCRIPTION#####
This function is a LPF for smoothing out signals or averaging
It uses a static buffer to store the last "n" readings, and each time it is called,
the oldest reading is discarded, the new reading is added, and the stack average
value is returned.
The buffer size is user-adjustable, but is constrained between 2 and 50
An initialization function fills the entire buffer with the input value. 
This is useful when the LPF function is called for the first time.
Note: To meet the definition of a true LPF, this function must be called         at regular intervals.
*/
//#####ФУНКЦИЯ ФИЛЬТРА НИЗКИХ ПРОХОДОВ#####
float LPF(int pinAnalogue, int bufferSize)
{
  //##Предпроцессор##
  #define bufferCap 75 //максимальная емкость буфера
  //##КОНСТАНТЫ##
  const int constMAXERRORS = 5; //максимальное количество ошибок, которые могут возникнуть
  //##ПЕРЕМЫЕ##
  float tempC; //var для хранения мгновенной температуры
  static float buffer[bufferCap]; //массив, который будет действовать как «стек» временных значений ***НУЖНО СЕЙЧАС ОН ДОЛЖЕН БЫТЬ СТАТИЧЕСКИМ?***
  float tempSum; //для хранения суммы температур
  float output; //возвращаемое значение ФНЧ, которое является средним значением температуры
  int errCounter = 0; //инициализируем счетчик ошибок равным 0

  //убедимся, что размер буфера находится в пределах диапазона
  bufferSize = constrain(bufferSize, 2, bufferCap);

  for (int i=0; i<bufferSize; i++) //цикл for для сохранения временных значений в буферном массиве
  {
    tempC = getTempFloat(pinAnalogue); //вызов функции для получения температуры от контакта
    if ((tempC > -errReturn) && (tempC < errReturn)) //нет ошибки
    {
      buffer[i] = tempC; //сохраняем значение температуры в массив
    }
    else //ошибка
    {
      errCounter++; //приращение счетчика количества ошибочных показаний
      i--; //уменьшаем счетчик, чтобы не пропускать индекс выборки
      if (errCounter > constMAXERRORS)
      {
        return(errReturn+1.0); // возвращаем значение ошибки + 1
      }//конец, если
    }//конец, если
    delay(25); //позволяем АЦП рассчитаться
  }//конец для

  //вычисляем текущее среднее значение стека
  tempSum = 0; //инициализируем итоговую сумму
  for (int i=0; i<bufferSize; i++) //проходим через массив стека
  {
    tempSum = tempSum + buffer[i]; //суммируем показания
  }//конец для

  //средняя сумма
  output = tempSum/bufferSize;

  return(output);
}//завершаем функцию ФНЧ

/*
#####GET TEMP FLOAT DESCRIPTION#####  
This function converts a thermistor reading into a corresponding temp in ^C
The thermistor is incorporated into a Voltage Divider Circuit:

   +Vref---[Thermistor]---+--[1.8K]---GND
                          |
                         ADC @ thermPin

ADC Values were externally calculated from the calibration table using:           ADC = 1023*10000/(Rtherm+10000)
The LUT is an array of integer constants containing the predicted ADC         values for all temperatures between +6^C to +40.5^C.
The array index starts at zero, which corresponds to a temperature of         +6^C.
A linear interpolation between the two closest entries is performed to         give a finer output resolution.
*/

//##КОНСТАНТЫ##
/*1K8*/
const int LUT_Therm[] PROGMEM = //LUT, содержащий значения АЦП
{
  223, 224, 225, 226, 227, 228, 229, 230, 231, 232,         //от 7^C до 7,9^C
  233, 234, 235, 236, 237, 238, 239, 240, 241, 242,
  244, 245, 246, 247, 248, 249, 250, 251, 252, 253,
  255, 256, 257, 258, 259, 260, 261, 263, 264, 265,
  266, 267, 268, 269, 271, 272, 273, 274, 275, 277,
  278, 279, 280, 281, 283, 284, 285, 286, 287, 289,
  290, 291, 292, 294, 295, 296, 297, 299, 300, 301,           // от 13 ^C до 13,9 ^C
  302, 304, 305, 306, 308, 309, 310, 312, 313, 314,
  315, 317, 318, 319, 321, 322, 323, 325, 326, 327,
  329, 330, 332, 333, 334, 336, 337, 338, 340, 341,
  343, 344, 345, 347, 348, 350, 351, 353, 354, 355,
  357, 358, 360, 361, 363, 364, 366, 367, 369, 370,
  371, 373, 374, 376, 377, 379, 380, 382, 383, 385,
  387, 388, 390, 391, 393, 394, 396, 397, 399, 400,
  402, 404, 405, 407, 408, 410, 411, 413, 415, 416,
  418, 419, 421, 423, 424, 426, 428, 429, 431, 433,
  434, 436, 438, 439, 441, 443, 444, 446, 448, 449,
  451, 453, 454, 456, 458, 460, 461, 463, 465, 466,
  468, 470, 472, 473, 475, 477, 479, 480, 482, 484,
  486, 488, 489, 491, 493, 495, 497, 498, 500, 502,
  504, 506, 507, 509, 511, 513, 515, 517, 519, 520,
  522, 524, 526, 528, 530, 532, 534, 535, 537, 539,
  541, 543, 545, 547, 549, 551, 553, 555, 557, 559,
  560, 562, 564, 566, 568, 570, 572, 574, 576, 578, 
  580, 582, 584, 586, 588, 590, 592, 594, 596, 598,
  600, 602, 604, 606, 608, 611, 613, 615, 617, 619,
  621, 623, 625, 627, 629, 631, 633, 635, 638, 640,
  642, 644, 646, 648, 650, 652, 655, 657, 659, 661,
  663, 665, 667, 670, 672, 674, 676, 678, 680, 683,
  685, 687, 689, 691, 694, 696, 698, 700, 702, 705,
  707, 709, 711, 714, 716, 718, 720, 723, 725, 727,
  729, 732, 734, 736, 738, 741, 743, 745, 747, 750, 
  752, 754, 757, 759, 761, 764, 766, 768, 771, 773,
  775, 778, 780, 782, 785, 787, 789, 792, 794, 796,
  799, 801, 803, 806, 808, 811, 813, 815, 818, 820,
  822, 825, 827, 830, 832, 834, 837, 839, 842, 844,
  847, 849, 851, 854, 856, 859, 861, 864, 866, 868,
  871, 873, 876, 878, 881, 883, 886, 888, 891, 893,
  896, 898, 901, 903, 906, 908, 911, 913, 916, 918, 
  921, 923, 926, 928, 931, 933, 936, 938, 941, 943,
  946, 948, 951, 953, 956, 958, 961, 963, 966, 969,
  971, 974, 976, 979, 981, 984, 987, 989, 992, 994,
  997, 999, 1002, 1005, 1007, 1010, 1012, 1015, 1017, 1020, //от 40°С до 49,9°С.
  1023                                                     //50^С
};
const int constLUTArraySize = sizeof(LUT_Therm)/sizeof(LUT_Therm[0]);

float getTempFloat (int thermPin)
{
  //##ПЕРЕМЫЕ##
  float _tempC; //промежуточные результаты + окончательное возвращаемое значение температуры
  int ADC_Lo; //нижнее значение соответствия АЦП
  int ADC_Hi; //более высокое значение соответствия АЦП
  float Temp_Lo; //нижнее число, соответствующее температуре
  int mapTemp_Lo; //нижнее число, которое будет введено в функцию карты
  float Temp_Hi; //более высокое число соответствует температуре
  int mapTemp_Hi; //большее число, которое будет введено в функцию карты

  //подготавливаем АЦП к этому конкретному аналоговому выводу, чтобы разобраться с мультиплексированием и т. д.
  analogRead(thermPin); 
  delay(10);
  // получаем необработанное значение АЦП из VDR
  int thermValue = analogRead(thermPin); 
  //Serial.println(thermValue);
  /*Serial.print( F("PinNo : ") );
  Serial.println(thermPin);*/
  Serial.print( F("ADC : ") );
  Serial.println(thermValue);

  //возвращаем фиктивное значение, если показания датчика выходят за пределы LUT
  if (thermValue < (int) pgm_read_word( &LUT_Therm[0] )) //меньше наименьшего значения АЦП
    _tempC = -errReturn-1; // фиктивное значение ниже диапазона

  else if (thermValue > (int) pgm_read_word(LUT_Therm[constLUTArraySize-1])) //больше максимального значения АЦП
    _tempC = errReturn+1; // фиктивное значение превышения диапазона

  else //значение попадает в диапазон LUT
  {
    int prev_lut_therm = 0;
    for (int i=0; i<constLUTArraySize; i++) //проходим через LUT и ищем совпадение
    {    
      int lut_therm = (int) pgm_read_word(LUT_Therm[i]);
      if (lut_therm > thermValue) //значение LUT больше показания
      {  
        //находим ближайшее более высокое значение АЦП
        ADC_Hi = lut_therm;
        //записываем ближайшую более высокую температуру
        Temp_Hi = float(i/10) + 13.0; //конвертируем во временную
        //получим ближайшую нижнюю температуру - с учетом нижней границы таблицы
        if (i != 0) //общий случай -> пока это не первая запись
        {
          ADC_Lo = prev_lut_therm; //сохраняем предыдущий элемент массива как нижний
          Temp_Lo = float(i/10) + 13.0 - 0.1; //конвертируем во временную
        }
        else //особый случай -> счетчик = 0, т.е. первая запись массива
        {
          ADC_Lo = lut_therm; //сохраняем первый элемент массива
          Temp_Lo = i - float(i/10) + 13.0; //конвертируем во временную
        }//конец, если
        //интерполируем значение температуры для большей точности
        //Обратите внимание, что функция карты не использует математические вычисления с плавающей запятой, поэтому целочисленные значения temp умножаются на 100, а затем результат впоследствии делится на 100
        mapTemp_Lo = Temp_Lo*100;
        mapTemp_Hi = Temp_Hi*100;
        _tempC = float(map(thermValue,ADC_Lo,ADC_Hi,mapTemp_Lo,mapTemp_Hi))/100;
        break;  //выходим из цикла после обнаружения совпадения
      }//конец, если
      prev_lut_therm = lut_therm;
    }//конец для
  }//конец, если
  return(_tempC);
}//завершаем функцию getTempFloat

Обратите внимание на технику объявления массива LUT_Therm с использованием пустых скобок [] (строка 135).

За этим следует размер массива const int (строка 182), который «вычисляется» из объявления массива: (Общий размер массива) / (размер одного элемента массива). Если вы когда-нибудь измените таблицу поиска, вам не придется считать элементы вручную. Позвольте компилятору сделать это за вас! :)


[1] Хотя IDE сообщает об увеличении размера двоичного эскиза, общий загружаемый размер не меняется:

Текстовые данные ключевого слова PROGMEM bss
№ 5948 894 483
Да 6810 32 483

Как видите, 862 байта этой таблицы перемещаются из раздела data в раздел text. Загруженные файлы HEX идентичны и имеют размер 93 737 байт.

,

У меня есть страница о [вставке вещей в PROGMEM](http://www.gammon.com.au/progmem)., @Nick Gammon

«_Размер двоичного эскиза увеличивается на ту же величину_». Это неверно. Инициализированные массивы потребляют одинаковое количество флэш-памяти независимо от того, являются ли они PROGMEM или нет., @Edgar Bonet

@EdgarBonet, правда, но *сообщаемый* размер программы увеличивается на ту же величину. Попробуй это! :) Просто удалите ключевое слово PROGMEM. Хотя он не будет работать правильно, размер, указанный в журнале сборки, изменится. Должно быть, это как-то связано с тем, какие разделы добавляются?, @slash-dev

...теперь мне интересно, какое число правильное..., @slash-dev

Ваш avr-размер должен быть нарушен. Используя PROGMEM, я получаю программу размером 6646 байт (.text: 6604 байта, .data: 42 байта). Если я удалю PROGMEM, я получу 6644 байта (.text: 5606, .data: 1038). Я проверил размер HEX-файла (того, что фактически переносится во флэш-память Arduino), и в обоих случаях заявленный размер программы верен. Пожалуйста, исправьте свой ответ., @Edgar Bonet

@EdgarBonet, мой размер avr не нарушен. Возможно, IDE сообщает не так, как вы ожидали. Я добавил сноску с подробностями в ответ на ваш комментарий. Как я и подозревал, это результат того, как IDE добавляет разделы., @slash-dev


4

Возможно, вам не хватает оперативной памяти.

Только в справочной таблице у вас 431 целое число. Это соответствует 862 байтам SRAM. У вас также есть буфер из 75 чисел с плавающей запятой, что соответствует 300 байтам. Это в дополнение ко всем остальным переменным и вызовам функций.

Arduino Uno имеет только 2 КБ SRAM.

Решения:
1) Переместите LUT во Flash (т. е. в пространство программы), используя ключевое слово PROGMEM (непосредственно перед знаком равенства):

const int LUT_Therm[constLUTArraySize] PROGMEM = //LUT, содержащий значения АЦП

2) Используйте Arduino с большим объемом SRAM.

  • Arduino Mega — SRAM 8 КБ
  • Arduino ZERO — SRAM 32 КБ
  • Arduino Due — SRAM 96 КБ
  • Arduino MKR 1000/1010/1400/ZERO — SRAM 32 КБ
,

2

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

Во-первых, с LPF() есть две проблемы. В описании указано, что это фильтр нижних частот с скользящим средним. Это ошибочно: катящийся среднее значение будет принимать единственное значение, а затем сообщать среднее значение последние n показания. Эта функция, напротив, принимает n показаний и сообщает их среднее значение. Это имеет большое значение: скользящее среднее необходимо будет хранить последние значения n в статической памяти, в то время как ваш функция делает это без уважительной причины. Ты просто тратишь 300 байт оперативной памяти.

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

/*
* Возьмите «подсчет» показаний температуры со «булавки» и верните их среднее значение.
* Возвращает NaN (не число), если слишком много показаний ошибочны.
*/
float getAvgTemp(int pin, int count)
{
  const int MAXERRORS = 5; // максимальное количество ошибок, которые могут возникнуть
  float temp;              // текущее показание температуры
  float tempSum = 0;       // сумма температур
  int errCounter = 0;      // количество ошибочных показаний

  for (int i = 0; i < count; i++) {

    // Попытаемся получить правильное чтение.
    do {
      temp = getTempFloat(pin);
      if (isnan(temp))
        errCounter++;
      delay(25); // позволяем АЦП рассчитаться
    } while (isnan(temp) && errCounter <= MAXERRORS);

    // Слишком много ошибок: вернуть ошибку.
    if (errCounter > MAXERRORS)
      return NAN;

    tempSum += temp;
  }

  return tempSum / count;
}

Здесь я использую NaN (а не число) в качестве индикатора ошибки, как есть семантически более понятно, чем случайное значение, выходящее за пределы диапазона.

В getTempFloat() также есть несколько ошибок:

  • pgm_read_word(LUT_Therm[constLUTArraySize-1]) и pgm_read_word(LUT_Therm[i]) не будет работать: вам придется пройти pgm_read_word() адрес, где читать.
  • float(i/10) не делает то, что вы хотите: он вычисляет i/10 как целочисленное деление (т.е. отбрасывание дробной части), а затем преобразует результат в число с плавающей запятой. Если вам нужно деление с плавающей запятой, вы должны убедиться, что хотя бы один из аргументов является числом с плавающей запятой. Обычная идиома — i/10.0.

Вот версия getTempFloat() с исправленными этими проблемами, и также несколько упрощенно:

float getTempFloat(int pin)
{
  int i, lutval, prev_lutval;

  // Снять аналоговое показание.
  analogRead(pin);  // фиктивное чтение для урегулирования MUX
  delay(10);
  int adc = analogRead(pin);
  Serial.print(F("ADC: "));
  Serial.println(adc);

  // Найдите i такое, что adc находится между LUT[i-1] и LUT[i].
  for (i = 0; i < constLUTArraySize; i++) {
    prev_lutval = lutval;
    lutval = pgm_read_word(&LUT_Therm[i]);
    if (lutval >= adc) break;
  }

  // Особый случай: adc == LUT[0].
  if (i == 0 && adc == lutval)
    return 13.0;

  // Сообщаем об ошибке, если значение выходит за пределы диапазона.
  if (i == 0 || i == constLUTArraySize)
    return NAN;

  // Возвращаем интерполированную температуру.
  float fraction = float(adc - prev_lutval) / (lutval - prev_lutval);
  return 13.0 + (i-1 + fraction) / 10.0;
}

Дополнение

Я заметил еще несколько проблем и несоответствий в вашем программа.

Питание термистора от внутреннего источника

В setup() вы пишете

аналоговая ссылка(ВНУТРЕННЯЯ); //используем интервал напряжения 1,1В для Разрешение АЦП

а затем, в комментарии ниже, вы найдете схему

+Vref---[Термистор]---+--[1,8K]---Земля

Это говорит о том, что вы питаете делитель напряжения от внутреннего опорное напряжение MCU. Не следует этого делать, так как в таблице данных утверждает: «Обратите внимание, что VREF является источником с высоким импедансом, и только к системе должна быть подключена емкостная нагрузка».

Взаимосвязь между показаниями АЦП и сопротивлением термистора

В том же комментарии, что и выше, вы написали:

АЦП = 1023*10000/(Rtherm+10000)

На самом деле коэффициент равен 1024, а не 1023. Опять же, см. таблицу данных. И число 10000 должно быть 1800, т.е. сопротивление, которое вы поставили между входом АЦП и GND. Я понимаю, что у вас может быть просто изменил значение и забыл обновить один из комментариев. Но ты следует рассматривать вводящий в заблуждение комментарий как ошибку, особенно если это не так. соответствует фактическому коду.

Для такого рода измерений вы получите максимальную точность, если резистор имеет номинал, близкий к сопротивлению термистора. комментарий в начале программы говорит, что ваш термистор неисправен. 10 кОм при 25°C. Это означает, что если вас больше всего интересует В диапазоне около 25°C понижение сопротивления 10 кОм лучше, чем 1,8 кОм.

Связь между индексом массива и температурой

Комментарий перед LUT гласит:

Индекс массива начинается с нуля, что соответствует температуре +6^С

Затем в самой первой строке LUT есть комментарий

//от 7^C до 7,9^C

И позже реализация getTempFloat() предполагает индекс 0 означает 13°C. Тебе следует разобраться с этим и сделать все последовательный.

Кстати, ваши значения LUT доходят до 1023. Значение 1023 будет означает, что напряжение АЦП выше 1022,5/1024×ВREF. Который в свою очередь, это будет означать, что термистор имеет сопротивление ниже, чем 14,6 Ом (при условии понижения напряжения 10 кОм). Мне это кажется сомнительным.

Удаление LUT

Поскольку ваша программа должна работать очень медленно (constDelay = 3000), вы можете позволить себе выполнять в нем сложные вычисления. Вы можете получить то же самое точность с меньшим LUT, если вы используете более высокий порядок интерполяция. Для функции, выбранной с постоянным шагом, Кэтмалл – Ром сплайн его легко реализовать, и он намного лучше, чем линейная интерполяция.

Или можно вообще убрать LUT и использовать аналитическое выражение вместо. Если вы получили LUT из такого выражения, почему бы не использовать его в программе? Если LUT получен на основе экспериментальных данных, вы можете попробовать сделать эмпирическую подгонку, т. е. произвольную функцию, которая точно соответствует данные. Самая очевидная (хотя, возможно, и не лучшая) эмпирическая функция. будет полиномом. Ниже приведен пример такой функции, которая воспроизводит LUT с хорошей точностью, используя полином 6-й степени. Это предполагает, что первая запись LUT относится к температуре 7 °C:

float getTempFloat(int pin)
{
    const float T0 = 7;   // температура в начале диапазона
    int adc = analogRead(pin);
    float x = (adc-223.) / (1023.-223.) * 2 - 1;  // масштабируем до [-1:1]
    if (x < -1 || x > 1) return NAN;              // вне диапазона
    float y = 1.474 - x*(0.533 - x*(0.732 - x*0.458));
    return T0 + 26.107 + x*(19.287 - x*(3.596 - x*y));
}

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

,

Большое спасибо за ваши замечания, некоторые из которых я бы не принял во внимание. Да, я менял диапазон температур, поэтому некоторые комментарии не имеют смысла. Разве вызов аналога (внутренний) не использует 1,1 В для получения лучшего разрешения между шагами АЦП и не использует ли выход Arduino 3,3 В в качестве Vref, и я выяснил, что резистор 1 кОм дает мне диапазон 7-50 градусов Цельсия ( однако, как показано выше, я могу ошибаться!). Я использую это как регистратор температуры кожи, поэтому температура будет в диапазоне 30-40 градусов Цельсия. Я все еще не уверен, сколько измерений в минуту мне нужно, но интерполяция выглядит лучше. Спасибо, @EoinScully